Skip to content

Fold libmps_parser into libcuopt#1193

Open
mlubin wants to merge 5 commits into
NVIDIA:mainfrom
mlubin:mps-parser-fold
Open

Fold libmps_parser into libcuopt#1193
mlubin wants to merge 5 commits into
NVIDIA:mainfrom
mlubin:mps-parser-fold

Conversation

@mlubin
Copy link
Copy Markdown
Contributor

@mlubin mlubin commented May 8, 2026

Merge the standalone libmps_parser C++ library and the cuopt-mps-parser Python wheel into the main cuopt build, eliminating the separate distribution while keeping all functionality. The parser sources now live under cpp/src/io/ and the public headers under cpp/include/cuopt/linear_programming/io/, matching the new namespace cuopt::linear_programming::io.

C++:

  • Move cpp/libmps_parser/{src,include,tests}/ into the cuopt tree.
  • Drop the separate libmps_parser.so target; parser sources compile into libcuopt.so via cpp/src/io/CMakeLists.txt.
  • Hoist BZip2/ZLIB find_package + CUOPT_PARSER_WITH_{BZIP2,ZLIB} options to the top-level cpp build.
  • Rename namespace cuopt::mps_parser -> cuopt::linear_programming::io across all sources, headers, tests, and benchmarks.
  • Wire the gtest suite (mps_parser_test.cpp) into cpp/tests/linear_programming/CMakeLists.txt.

Python:

  • Move python/cuopt/cuopt/linear_programming/cuopt_mps_parser/ into python/cuopt/cuopt/linear_programming/mps_parser/, importable as cuopt.linear_programming.mps_parser (and re-exported as ParseMps/toDict at the linear_programming package level).
  • Fold data_model/ cython binding into the main cuopt wheel; relink data_model and solver cython modules to cuopt::cuopt.
  • Update cython .pxd extern paths and namespace strings to cuopt/linear_programming/io/ and cuopt::linear_programming::io.
  • Delete the standalone parser-wheel scaffolding (pyproject.toml, CMakeLists.txt, README, LICENSE, _version.py).
  • Update test imports across test_parser.py, test_lp_solver.py, test_incumbent_callbacks.py, test_cpu_only_execution.py, test_pdlp_warmstart.py, plus cuopt_self_host_client.py, problem.py, and regression/benchmark_scripts/utils.py.

Build / packaging / CI:

  • Drop the libmps_parser target and the FIND_MPS_PARSER_CPP flag from build.sh; help text and default action updated accordingly.
  • Delete conda/recipes/mps-parser/ and the libmps-parser output (plus all pin_subpackage references) in conda/recipes/libcuopt/recipe.yaml.
  • Drop cuopt-mps-parser =${version} from conda/recipes/cuopt/recipe.yaml.
  • Remove the wheel-build-cuopt-mps-parser job, mps_parser_filter, and ci/build_wheel_cuopt_mps_parser.sh from CI; clean up cuopt_mps_parser wheelhouse downloads/installs from the remaining ci/*.sh scripts.
  • Drop cuopt-mps-parser dependency entries from python/{cuopt,libcuopt, cuopt_self_hosted}/pyproject.toml and dependencies.yaml; remove the py_*_cuopt_mps_parser pyproject groups and depends_on_mps_parser.
  • Update Dockerfile and ci/release/update-version.sh package lists.

Migration instructions

MPS/QPS parsing functionality is now bundled directly into the cuopt Python package. The standalone cuopt-mps-parser package (and its cuopt_mps_parser and data_model import names) are no longer published.

If you depend on the parser, you need to update both your environment and your imports.

Environment

Conda users. Remove cuopt-mps-parser from your environment files; cuopt now bundles the parser:

# conda environment file (before)
dependencies:
  - cuopt=26.6
  - cuopt-mps-parser=26.6   # <- delete this line

# (after)
dependencies:
  - cuopt=26.6

pip users. Remove cuopt-mps-parser from your requirements.txt / pyproject.toml dependencies. cuopt (and libcuopt, cuopt-server,
cuopt-sh-client) no longer require it transitively:

 cuopt-cu13==26.6.*
-cuopt-mps-parser==26.6.*

Run pip uninstall cuopt-mps-parser in existing environments to clear the old install.

Code

The functions and classes are unchanged — only the import paths moved. Update as follows:

Before After
import cuopt_mps_parser from cuopt.linear_programming import mps_parser
cuopt_mps_parser.ParseMps(...) mps_parser.ParseMps(...)
cuopt_mps_parser.toDict(...) from cuopt.linear_programming.mps_parser import toDict then toDict(...)
cuopt_mps_parser.parser_wrapper.DataModel mps_parser.parser_wrapper.DataModel
from cuopt_mps_parser.utilities import InputValidationError from cuopt.linear_programming.mps_parser.utilities import InputValidationError
from data_model import DataModel from cuopt.linear_programming.data_model import DataModel

For convenience, ParseMps is also re-exported at the package level:

from cuopt.linear_programming import ParseMps

@mlubin mlubin requested a review from rgsl888prabhu May 8, 2026 20:23
@mlubin mlubin added breaking Introduces a breaking change improvement Improves an existing functionality labels May 8, 2026
@mlubin mlubin requested review from a team as code owners May 8, 2026 20:23
@mlubin mlubin requested review from aliceb-nv and yuwenchen95 May 8, 2026 20:23
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 8, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 8, 2026

/ok to test b71d4ae

@rgsl888prabhu
Copy link
Copy Markdown
Collaborator

/ok to test b71d4ae

@mlubin awesome, changes looks good, we may need to add docs, but we can add later, will keep eye on build if it requires any fix will address those.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes standalone mps_parser across build/CI/conda and migrates all C++/Python/tests/docs to cuopt::linear_programming::io parser/writer, integrating io sources into libcuopt, updating solver interfaces, and adjusting dependencies and workflows accordingly.

Changes

MPS IO unification and deprecation of standalone mps_parser

Layer / File(s) Summary
Core IO module and public API relocation
cpp/src/io/*, cpp/include/cuopt/linear_programming/io/*
Adds io sources, moves mps types/functions to cuopt::linear_programming::io, updates parser/writer/data_model, and exposes cython helper.
Solver and CLI wiring
cpp/include/.../solve.hpp, cpp/src/pdlp/*, cpp/src/mip_heuristics/solve.cu, cpp/cuopt_cli.cpp
Updates signatures/includes to accept io::mps_data_model_t and use io::parse_mps/write_mps.
Benchmarks and utilities
benchmarks/linear_programming/cuopt/*
Switches includes and types to io parser.
Build/Packaging cleanup
cpp/CMakeLists.txt, cpp/src/CMakeLists.txt, cpp/libmps_parser/*, conda/recipes/*, build.sh
Removes libmps_parser targets/outputs, adds io subdir, updates options, and drops mps-parser conda recipes.
Python bindings and packages
python/cuopt/.../linear_programming/*, python/cuopt/CMakeLists.txt, python/cuopt/pyproject.toml, python/libcuopt/pyproject.toml
Links Cython modules against cuopt, provides in-package mps_parser, removes external cuopt-mps-parser dependency/config.
Tests and examples
cpp/tests/*, python/*/tests/*, docs/*
Refactors all tests/docs to import/use io parser and types.
CI/Workflows/Docker
.github/workflows/*, ci/*, ci/docker/Dockerfile, dependencies.yaml
Deletes mps-parser jobs/filters, wheel scripts, constraints; updates pipelines to build/test remaining packages.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
python/cuopt/cuopt/linear_programming/mps_parser/utilities/__init__.py (1)

4-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a deprecation shim for legacy parser import paths before hard removal.

Line 4 switches consumers to the new module path, but this migration needs a temporary compatibility path (with DeprecationWarning + removal version) so existing cuopt_mps_parser imports don’t fail abruptly.

Suggested compatibility shim (example)
+ # python/cuopt/cuopt/linear_programming/cuopt_mps_parser/utilities/__init__.py
+ import warnings
+ from cuopt.linear_programming.mps_parser.utilities.exception_handler import (
+     InputRuntimeError,
+     InputValidationError,
+     OutOfMemoryError,
+     catch_mps_parser_exception,
+ )
+
+ warnings.warn(
+     "cuopt.linear_programming.cuopt_mps_parser.utilities is deprecated; "
+     "use cuopt.linear_programming.mps_parser.utilities. "
+     "This path will be removed in a future release.",
+     DeprecationWarning,
+     stacklevel=2,
+ )

As per coding guidelines: “For Python/public API breaking changes, require a DeprecationWarning path rather than removing immediately.”

🤖 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/mps_parser/utilities/__init__.py`
around lines 4 - 9, Add a deprecation shim module that re-exports the parser
exceptions and helper while emitting a DeprecationWarning pointing to the new
path and a removal version; specifically, create a tiny compatibility module
that imports InputRuntimeError, InputValidationError, OutOfMemoryError, and
catch_mps_parser_exception from cuopt.linear_programming.mps_parser.utilities
and re-exports them, and call warnings.warn(..., DeprecationWarning,
stacklevel=2) with a clear message mentioning the new import path and the
planned removal version so existing cuopt_mps_parser imports keep working
temporarily.
cpp/src/parsers/mps_writer.cpp (2)

56-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve CSR offsets when the matrix has zero stored nonzeros.

create_view() currently skips set_csr_constraint_matrix() when A_values is empty, but write() still assumes constraint_matrix_offsets is populated for every row. A valid zero-NNZ matrix then drops its row offsets and can hit out-of-bounds reads when serializing columns.

Suggested fix
-  if (!A_values.empty()) {
+  if (!A_offsets.empty()) {
     view.set_csr_constraint_matrix(A_values.data(),
                                    static_cast<i_t>(A_values.size()),
                                    A_indices.data(),
                                    static_cast<i_t>(A_indices.size()),
                                    A_offsets.data(),
                                    static_cast<i_t>(A_offsets.size()));
   }
🤖 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/parsers/mps_writer.cpp` around lines 56 - 63, create_view() currently
skips calling set_csr_constraint_matrix() when A_values is empty, which causes
constraint_matrix_offsets to be missing for zero-NNZ matrices and leads to
out-of-bounds reads in write(); always register the CSR structure so offsets are
preserved even if there are no stored nonzeros by calling
set_csr_constraint_matrix() whenever A_offsets (or the number of rows) is
present: pass A_offsets.data() and its size, and if A_values/A_indices are empty
pass nullptr (or .data() from empty vectors) and zero sizes for the value/index
arrays so the offsets are stored; update the call site in create_view() (the
block that currently guards set_csr_constraint_matrix()) to unconditionally set
the CSR offsets (while handling empty value/index arrays) so write() finds a
populated constraint_matrix_offsets.

387-400: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the declared row names in the RANGES section.

ROWS and RHS honor problem_.get_row_names(), but RANGES hard-codes R${i}. For named ranged constraints, the emitted RNG1 entries no longer match any declared row, so the written MPS is wrong.

Suggested fix
   for (size_t i = 0; i < (size_t)n_constraints; i++) {
     if (constraint_lower_bounds[i] != -std::numeric_limits<f_t>::infinity() &&
         constraint_upper_bounds[i] != std::numeric_limits<f_t>::infinity() &&
         constraint_lower_bounds[i] != constraint_upper_bounds[i]) {
       if (!has_ranges) {
         mps_file << "RANGES\n";
         has_ranges = true;
       }
-      std::string row_name = "R" + std::to_string(i);
+      std::string row_name = i < problem_.get_row_names().size()
+                               ? problem_.get_row_names()[i]
+                               : "R" + std::to_string(i);
       mps_file << "    RNG1      " << row_name << " "
                << (constraint_upper_bounds[i] - constraint_lower_bounds[i]) << "\n";
     }
   }
🤖 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/parsers/mps_writer.cpp` around lines 387 - 400, The RANGES block
currently uses a generated name "R"+i which can mismatch declared rows; change
the logic in mps_writer.cpp (the RANGES emission around has_ranges and the RNG1
line) to use the actual row name from problem_.get_row_names() (or the same row
name array used by the ROWS and RHS sections) for the variable row_name instead
of hard-coding "R"+std::to_string(i), and keep the same range value expression
(constraint_upper_bounds[i] - constraint_lower_bounds[i]) and the has_ranges
flag behavior.
python/cuopt/cuopt/linear_programming/mps_parser/parser.py (1)

12-50: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate ParseMps arguments before entering the Cython/C++ layer.

At Line [12], invalid inputs (e.g., non-path mps_file_path, non-bool fixed_mps_format) will fail later with less actionable errors.

Suggested patch
+import os
 import numpy as np
@@
 `@catch_mps_parser_exception`
 def ParseMps(mps_file_path, fixed_mps_format=False):
@@
-    return parser_wrapper.ParseMps(mps_file_path, fixed_mps_format)
+    if not isinstance(fixed_mps_format, bool):
+        raise TypeError("fixed_mps_format must be a bool")
+    if not isinstance(mps_file_path, (str, os.PathLike)):
+        raise TypeError("mps_file_path must be a path-like value")
+    mps_file_path = os.fspath(mps_file_path)
+    if not mps_file_path:
+        raise ValueError("mps_file_path cannot be empty")
+    return parser_wrapper.ParseMps(mps_file_path, fixed_mps_format)

As per coding guidelines, "Flag missing input validation at library and server boundaries" and "Error messages that expose internals vs. user-actionable messages."

🤖 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/mps_parser/parser.py` around lines 12 -
50, The public ParseMps wrapper does not validate inputs before delegating to
parser_wrapper.ParseMps; add pre-call validation in ParseMps to ensure
mps_file_path is a str or os.PathLike (or at minimum non-empty string) and
fixed_mps_format is a bool, raising clear TypeError/ValueError messages if
checks fail, then call parser_wrapper.ParseMps with the validated values;
reference the ParseMps function and the parser_wrapper.ParseMps call when
locating where to add these checks.
cpp/include/cuopt/linear_programming/parsers/writer.hpp (1)

10-26: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a direct #include <string> to keep this header self-contained.

Line 26 uses std::string in the function signature, but this header does not directly include <string>. While std::string is available transitively through data_model_view.hpp, the IWYU principle requires direct inclusion of what is used to avoid fragility from include-order changes or future dependency modifications.

Proposed patch
 `#pragma` once
 
 `#include` <cuopt/linear_programming/parsers/data_model_view.hpp>
+#include <string>
🤖 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/linear_programming/parsers/writer.hpp` around lines 10 -
26, The header declares the template function write_mps(...) which uses
std::string but does not include <string>, violating IWYU; make this header
self-contained by adding a direct `#include` <string> at the top of
cpp/include/cuopt/linear_programming/parsers/writer.hpp (above or alongside the
existing includes) so the declaration of write_mps(const data_model_view_t<i_t,
f_t>&, const std::string&) does not rely on transitive includes.
cpp/src/pdlp/utilities/cython_solve.cu (1)

203-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard batch inputs before memory-based thread calculation.

Line 232 can divide by zero (needed_memory), and Line 221 dereferences raw pointers without null checks. Empty batches or malformed entries can crash batch solve.

🔧 Suggested fix
 static int compute_max_thread(
   const std::vector<cuopt::linear_programming::parsers::data_model_view_t<int, double>*>&
     data_models)
 {
   constexpr std::size_t max_total = 4;
+  cuopt_expects(!data_models.empty(),
+                error_type_t::ValidationError,
+                "Batch solve requires at least one problem.");

   // Computing on the total_mem as LP is suppose to run on a single exclusive GPU
   // On CPU-only hosts cudaMemGetInfo will fail; fall back to single-threaded batch.
   std::size_t free_mem, total_mem;
   auto cuda_err = cudaMemGetInfo(&free_mem, &total_mem);
@@
   // Approximate the necessary memory for each problem
   std::size_t needed_memory = 0;
-  for (const auto data_model : data_models) {
+  for (const auto* data_model : data_models) {
+    cuopt_expects(data_model != nullptr,
+                  error_type_t::ValidationError,
+                  "Batch solve received a null data model pointer.");
     const int nb_variables   = data_model->get_objective_coefficients().size();
     const int nb_constraints = data_model->get_constraint_bounds().size();
@@
                      8;
   }
+  cuopt_expects(needed_memory > 0,
+                error_type_t::ValidationError,
+                "Batch solve cannot estimate memory for empty/invalid models.");

-  const int res = std::min(max_total, std::min(total_mem / needed_memory, data_models.size()));
+  const int res = static_cast<int>(
+    std::min(max_total, std::min(total_mem / needed_memory, data_models.size())));
   cuopt_expects(
     res > 0, error_type_t::RuntimeError, "Problems too big to be solved in batch mode.");

As per coding guidelines: "Flag missing input validation at library and server boundaries".

🤖 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/pdlp/utilities/cython_solve.cu` around lines 203 - 237,
compute_max_thread currently dereferences raw pointers from data_models and
divides by needed_memory without validating inputs; add upfront guards to (1)
return 0 or throw if data_models.empty(), (2) skip or reject null entries in
data_models before using data_model->get_objective_coefficients(),
get_constraint_bounds(), get_constraint_matrix_values(),
get_constraint_matrix_indices(), and get_constraint_matrix_offsets(), and (3)
after summing needed_memory, check if needed_memory == 0 and handle it (return 0
or raise a clear error) before computing total_mem / needed_memory and calling
std::min; ensure the error path uses cuopt_expects or an appropriate
error_type_t and provides a descriptive message.
🤖 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 `@benchmarks/linear_programming/cuopt/initial_problem_check.hpp`:
- Line 33: The header is not self-contained because it uses
cuopt::linear_programming::parsers::mps_data_model_t in the function signature
without including its declaration; fix this by adding an `#include` of the header
that defines cuopt::linear_programming::parsers::mps_data_model_t at the top of
initial_problem_check.hpp (or, if the type is suitable for a forward
declaration, add a proper forward declaration of that template/type in the
cuopt::linear_programming::parsers namespace), ensuring the declaration for
mps_data_model_t is present so the function signature compiles without relying
on transitive includes.

In `@build.sh`:
- Line 66: The BUILD_DIRS assignment contains a stale/undefined variable
CUOPT_SERVICE_CLIENT_BUILD_DIR which results in an empty item and makes the
clean target out of sync; edit the BUILD_DIRS definition to remove
CUOPT_SERVICE_CLIENT_BUILD_DIR or replace it with the correct variable name (if
it was renamed elsewhere) so BUILD_DIRS only contains defined variables like
LIBCUOPT_BUILD_DIR, CUOPT_BUILD_DIR, CUOPT_SERVER_BUILD_DIR,
CUOPT_SH_CLIENT_BUILD_DIR, PY_LIBCUOPT_BUILD_DIR, and DOCS_BUILD_DIR; verify the
clean target uses the updated BUILD_DIRS after the change.

In
`@cpp/include/cuopt/linear_programming/parsers/utilities/cython_mps_parser.hpp`:
- Around line 10-18: The header declares call_parse_mps(const std::string&,
bool) but doesn't include <string>, so make the header self-contained by adding
the missing include: add `#include` <string> at the top of
cuopt::cython::cython_mps_parser.hpp (so call_parse_mps and any other uses of
std::string in this header are valid without relying on transitive includes).

In `@cpp/src/pdlp/cuopt_c.cpp`:
- Line 29: Remove the file-scope using namespace directives and replace them
with explicit qualifications or narrow scoped using-declarations: remove
occurrences of "using namespace cuopt::linear_programming::parsers" and the
other file-scope "using namespace cuopt::linear_programming", then update all
uses of parser/LP symbols (e.g., mps_data_model_t, parse_mps) to fully qualified
names like cuopt::linear_programming::parsers::mps_data_model_t and
cuopt::linear_programming::parsers::parse_mps, or introduce local using
declarations inside functions/blocks for only the few symbols used to avoid
polluting global scope. Ensure every reference to types and functions from
cuopt::linear_programming is explicitly qualified or locally imported.

In `@python/cuopt/cuopt/linear_programming/__init__.py`:
- Line 6: The package root currently only re-exports ParseMps; add a re-export
for toDict so users can access cuopt.linear_programming.toDict as documented.
Import toDict from its defining module (e.g.,
cuopt.linear_programming.mps_parser where ParseMps is imported) and include it
in the module exports (alongside ParseMps and any __all__ list) so the symbol is
available at the package root.

In `@python/cuopt/cuopt/linear_programming/mps_parser/__init__.py`:
- Line 4: Add a deprecation shim module named cuopt_mps_parser that re-exports
the new symbols and emits a DeprecationWarning telling users to migrate to
cuopt.linear_programming.mps_parser; specifically, import ParseMps and toDict
from cuopt.linear_programming.mps_parser, call warnings.warn("cuopt_mps_parser
is deprecated; use cuopt.linear_programming.mps_parser instead and will be
removed in X.Y", DeprecationWarning, stacklevel=2) (replace X.Y with the planned
removal version), and set __all__ = ["ParseMps","toDict"] so existing imports
continue to work while warning users.

---

Outside diff comments:
In `@cpp/include/cuopt/linear_programming/parsers/writer.hpp`:
- Around line 10-26: The header declares the template function write_mps(...)
which uses std::string but does not include <string>, violating IWYU; make this
header self-contained by adding a direct `#include` <string> at the top of
cpp/include/cuopt/linear_programming/parsers/writer.hpp (above or alongside the
existing includes) so the declaration of write_mps(const data_model_view_t<i_t,
f_t>&, const std::string&) does not rely on transitive includes.

In `@cpp/src/parsers/mps_writer.cpp`:
- Around line 56-63: create_view() currently skips calling
set_csr_constraint_matrix() when A_values is empty, which causes
constraint_matrix_offsets to be missing for zero-NNZ matrices and leads to
out-of-bounds reads in write(); always register the CSR structure so offsets are
preserved even if there are no stored nonzeros by calling
set_csr_constraint_matrix() whenever A_offsets (or the number of rows) is
present: pass A_offsets.data() and its size, and if A_values/A_indices are empty
pass nullptr (or .data() from empty vectors) and zero sizes for the value/index
arrays so the offsets are stored; update the call site in create_view() (the
block that currently guards set_csr_constraint_matrix()) to unconditionally set
the CSR offsets (while handling empty value/index arrays) so write() finds a
populated constraint_matrix_offsets.
- Around line 387-400: The RANGES block currently uses a generated name "R"+i
which can mismatch declared rows; change the logic in mps_writer.cpp (the RANGES
emission around has_ranges and the RNG1 line) to use the actual row name from
problem_.get_row_names() (or the same row name array used by the ROWS and RHS
sections) for the variable row_name instead of hard-coding
"R"+std::to_string(i), and keep the same range value expression
(constraint_upper_bounds[i] - constraint_lower_bounds[i]) and the has_ranges
flag behavior.

In `@cpp/src/pdlp/utilities/cython_solve.cu`:
- Around line 203-237: compute_max_thread currently dereferences raw pointers
from data_models and divides by needed_memory without validating inputs; add
upfront guards to (1) return 0 or throw if data_models.empty(), (2) skip or
reject null entries in data_models before using
data_model->get_objective_coefficients(), get_constraint_bounds(),
get_constraint_matrix_values(), get_constraint_matrix_indices(), and
get_constraint_matrix_offsets(), and (3) after summing needed_memory, check if
needed_memory == 0 and handle it (return 0 or raise a clear error) before
computing total_mem / needed_memory and calling std::min; ensure the error path
uses cuopt_expects or an appropriate error_type_t and provides a descriptive
message.

In `@python/cuopt/cuopt/linear_programming/mps_parser/parser.py`:
- Around line 12-50: The public ParseMps wrapper does not validate inputs before
delegating to parser_wrapper.ParseMps; add pre-call validation in ParseMps to
ensure mps_file_path is a str or os.PathLike (or at minimum non-empty string)
and fixed_mps_format is a bool, raising clear TypeError/ValueError messages if
checks fail, then call parser_wrapper.ParseMps with the validated values;
reference the ParseMps function and the parser_wrapper.ParseMps call when
locating where to add these checks.

In `@python/cuopt/cuopt/linear_programming/mps_parser/utilities/__init__.py`:
- Around line 4-9: Add a deprecation shim module that re-exports the parser
exceptions and helper while emitting a DeprecationWarning pointing to the new
path and a removal version; specifically, create a tiny compatibility module
that imports InputRuntimeError, InputValidationError, OutOfMemoryError, and
catch_mps_parser_exception from cuopt.linear_programming.mps_parser.utilities
and re-exports them, and call warnings.warn(..., DeprecationWarning,
stacklevel=2) with a clear message mentioning the new import path and the
planned removal version so existing cuopt_mps_parser imports keep working
temporarily.
🪄 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: 717ef8bd-c8a3-4523-8dbd-484dd68c62e0

📥 Commits

Reviewing files that changed from the base of the PR and between b311a99 and b71d4ae.

📒 Files selected for processing (120)
  • .github/workflows/build.yaml
  • .github/workflows/pr.yaml
  • benchmarks/linear_programming/cuopt/benchmark_helper.hpp
  • benchmarks/linear_programming/cuopt/initial_problem_check.hpp
  • benchmarks/linear_programming/cuopt/run_mip.cpp
  • benchmarks/linear_programming/cuopt/run_pdlp.cu
  • build.sh
  • ci/build_python.sh
  • ci/build_wheel_cuopt.sh
  • ci/build_wheel_cuopt_mps_parser.sh
  • ci/build_wheel_libcuopt.sh
  • ci/docker/Dockerfile
  • ci/release/update-version.sh
  • ci/test_self_hosted_service.sh
  • ci/test_wheel_cuopt.sh
  • ci/test_wheel_cuopt_server.sh
  • conda/recipes/cuopt/recipe.yaml
  • conda/recipes/libcuopt/recipe.yaml
  • conda/recipes/mps-parser/conda_build_config.yaml
  • conda/recipes/mps-parser/recipe.yaml
  • cpp/CMakeLists.txt
  • cpp/cuopt_cli.cpp
  • cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
  • cpp/include/cuopt/linear_programming/parsers/data_model_view.hpp
  • cpp/include/cuopt/linear_programming/parsers/mps_data_model.hpp
  • cpp/include/cuopt/linear_programming/parsers/mps_writer.hpp
  • cpp/include/cuopt/linear_programming/parsers/parser.hpp
  • cpp/include/cuopt/linear_programming/parsers/utilities/cython_mps_parser.hpp
  • cpp/include/cuopt/linear_programming/parsers/writer.hpp
  • cpp/include/cuopt/linear_programming/solve.hpp
  • cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp
  • cpp/libmps_parser/CMakeLists.txt
  • cpp/libmps_parser/cmake/thirdparty/get_gtest.cmake
  • cpp/libmps_parser/src/utilities/cython_mps_parser.cpp
  • cpp/libmps_parser/tests/CMakeLists.txt
  • cpp/libmps_parser/tests/utilities/common_utils.hpp
  • cpp/src/CMakeLists.txt
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/parsers/CMakeLists.txt
  • cpp/src/parsers/data_model_view.cpp
  • cpp/src/parsers/mps_data_model.cpp
  • cpp/src/parsers/mps_parser.cpp
  • cpp/src/parsers/mps_parser_internal.hpp
  • cpp/src/parsers/mps_writer.cpp
  • cpp/src/parsers/parser.cpp
  • cpp/src/parsers/utilities/cython_mps_parser.cpp
  • cpp/src/parsers/utilities/error.hpp
  • cpp/src/parsers/writer.cpp
  • cpp/src/pdlp/cpu_optimization_problem.cpp
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/src/pdlp/optimization_problem.cu
  • cpp/src/pdlp/solve.cu
  • cpp/src/pdlp/solve.cuh
  • cpp/src/pdlp/utilities/cython_solve.cu
  • cpp/tests/CMakeLists.txt
  • cpp/tests/dual_simplex/unit_tests/solve.cpp
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • cpp/tests/linear_programming/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • cpp/tests/linear_programming/mps_parser_test.cpp
  • cpp/tests/linear_programming/pdlp_test.cu
  • cpp/tests/linear_programming/unit_tests/optimization_problem_test.cu
  • cpp/tests/linear_programming/unit_tests/presolve_test.cu
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
  • cpp/tests/mip/bounds_standardization_test.cu
  • cpp/tests/mip/cuts_test.cu
  • cpp/tests/mip/determinism_test.cu
  • cpp/tests/mip/doc_example_test.cu
  • cpp/tests/mip/elim_var_remap_test.cu
  • cpp/tests/mip/feasibility_jump_tests.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/tests/mip/integer_with_real_bounds.cu
  • cpp/tests/mip/load_balancing_test.cu
  • cpp/tests/mip/mip_utils.cuh
  • cpp/tests/mip/miplib_test.cu
  • cpp/tests/mip/multi_probe_test.cu
  • cpp/tests/mip/presolve_test.cu
  • cpp/tests/mip/problem_test.cu
  • cpp/tests/mip/semi_continuous_test.cu
  • cpp/tests/mip/server_test.cu
  • cpp/tests/mip/termination_test.cu
  • cpp/tests/mip/unit_test.cu
  • cpp/tests/qp/unit_tests/no_constraints.cu
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • cpp/tests/utilities/inline_mps_test_utils.hpp
  • dependencies.yaml
  • python/cuopt/CMakeLists.txt
  • python/cuopt/cuopt/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/LICENSE
  • python/cuopt/cuopt/linear_programming/README.md
  • python/cuopt/cuopt/linear_programming/__init__.py
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/VERSION
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/__init__.py
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/_version.py
  • python/cuopt/cuopt/linear_programming/data_model/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
  • python/cuopt/cuopt/linear_programming/mps_parser/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/mps_parser/__init__.py
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.pxd
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.py
  • python/cuopt/cuopt/linear_programming/mps_parser/parser_wrapper.pyx
  • python/cuopt/cuopt/linear_programming/mps_parser/utilities/__init__.py
  • python/cuopt/cuopt/linear_programming/mps_parser/utilities/exception_handler.py
  • python/cuopt/cuopt/linear_programming/problem.py
  • python/cuopt/cuopt/linear_programming/pyproject.toml
  • python/cuopt/cuopt/linear_programming/solver/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/solver/solver.py
  • python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py
  • python/cuopt/cuopt/tests/linear_programming/test_incumbent_callbacks.py
  • python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
  • python/cuopt/cuopt/tests/linear_programming/test_parser.py
  • python/cuopt/pyproject.toml
  • python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
  • python/cuopt_self_hosted/pyproject.toml
  • python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
  • python/libcuopt/pyproject.toml
  • regression/benchmark_scripts/utils.py
💤 Files with no reviewable changes (27)
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/VERSION
  • cpp/libmps_parser/cmake/thirdparty/get_gtest.cmake
  • ci/test_wheel_cuopt_server.sh
  • python/cuopt/cuopt/linear_programming/LICENSE
  • ci/build_wheel_libcuopt.sh
  • python/cuopt/cuopt/linear_programming/README.md
  • conda/recipes/cuopt/recipe.yaml
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/init.py
  • ci/release/update-version.sh
  • python/cuopt/CMakeLists.txt
  • cpp/libmps_parser/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/_version.py
  • python/cuopt/pyproject.toml
  • ci/build_wheel_cuopt_mps_parser.sh
  • ci/docker/Dockerfile
  • ci/test_self_hosted_service.sh
  • cpp/libmps_parser/tests/CMakeLists.txt
  • conda/recipes/mps-parser/conda_build_config.yaml
  • conda/recipes/mps-parser/recipe.yaml
  • python/libcuopt/pyproject.toml
  • python/cuopt/cuopt/linear_programming/CMakeLists.txt
  • cpp/libmps_parser/tests/utilities/common_utils.hpp
  • python/cuopt_self_hosted/pyproject.toml
  • python/cuopt/cuopt/linear_programming/pyproject.toml
  • dependencies.yaml
  • ci/test_wheel_cuopt.sh
  • cpp/libmps_parser/src/utilities/cython_mps_parser.cpp

Comment thread benchmarks/linear_programming/cuopt/initial_problem_check.hpp Outdated
Comment thread build.sh
Comment thread cpp/include/cuopt/linear_programming/parsers/utilities/cython_mps_parser.hpp Outdated
Comment thread cpp/src/pdlp/cuopt_c.cpp Outdated
Comment thread python/cuopt/cuopt/linear_programming/__init__.py
Comment thread python/cuopt/cuopt/linear_programming/mps_parser/__init__.py
@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 8, 2026

/ok to test 4ef676e

@mlubin mlubin force-pushed the mps-parser-fold branch from 4ef676e to 76257b4 Compare May 8, 2026 21:01
@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 8, 2026

/ok to test 76257b4

Comment thread cpp/include/cuopt/linear_programming/parsers/data_model_view.hpp Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cpp/include/cuopt/linear_programming/solve.hpp (1)

61-63: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align Doxygen parameter descriptions with migrated io::mps_data_model_t signatures

The @param docs still describe legacy/problem-type inputs while the signatures now take cuopt::linear_programming::io::mps_data_model_t (Lines 74, 110, 143). This can mislead callers of this public header.

Suggested doc update
- * `@param`[in] mps_data_model  An optimization_problem_t<i_t, f_t> object with a
- * representation of a linear program
+ * `@param`[in] mps_data_model  An io::mps_data_model_t<i_t, f_t> object with a
+ * representation of a linear program
@@
- * `@param`[in] user_problem  A dual_simplex::user_problem_t<i_t, f_t> object with a
- * representation of a linear program.
+ * `@param`[in] mps_data_model  An io::mps_data_model_t<i_t, f_t> object with a
+ * representation of a linear program.
@@
- * `@param`[in] mps_data_model  An optimization_problem_t<i_t, f_t> object with a
- * representation of a linear program
+ * `@param`[in] mps_data_model  An io::mps_data_model_t<i_t, f_t> object with a
+ * representation of a linear program

As per coding guidelines: “Verify parameter descriptions match actual types/behavior” for cpp/include/cuopt/**/*.

Also applies to: 96-100, 136-138, 74-75, 110-110, 143-143

🤖 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/linear_programming/solve.hpp` around lines 61 - 63, Doxygen
`@param` entries for the functions in solve.hpp incorrectly reference the legacy
optimization_problem_t<i_t,f_t>; update the parameter descriptions to match the
current type cuopt::linear_programming::io::mps_data_model_t and its semantics:
change the description for mps_data_model to state it is a
cuopt::linear_programming::io::mps_data_model_t representing the LP problem
(including any expected ownership/constness and whether it contains constraints,
objective, and variable metadata), and ensure handle_ptr doc refers to
raft::handle_t with its CUDA stream; apply the same replacement to all affected
docblocks (the occurrences around the symbols/params mps_data_model and
handle_ptr at the other comment locations).
cpp/src/pdlp/utilities/cython_solve.cu (1)

203-233: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard zero-memory/empty-batch path before thread-count calculation

Line 231 computes total_mem / needed_memory without validating needed_memory > 0. With an empty batch (or empty data models), this can trigger undefined behavior before cuopt_expects runs.

Suggested fix
 static int compute_max_thread(
   const std::vector<cuopt::linear_programming::io::data_model_view_t<int, double>*>& data_models)
 {
+  if (data_models.empty()) { return 1; }
   constexpr std::size_t max_total = 4;
@@
   std::size_t needed_memory = 0;
@@
+  cuopt_expects(
+    needed_memory > 0,
+    error_type_t::ValidationError,
+    "Batch solve requires non-empty problem models.");
+
   const int res = std::min(max_total, std::min(total_mem / needed_memory, data_models.size()));
@@
 std::pair<std::vector<std::unique_ptr<solver_ret_t>>, double> call_batch_solve(
   std::vector<cuopt::linear_programming::io::data_model_view_t<int, double>*> data_models,
   cuopt::linear_programming::solver_settings_t<int, double>* solver_settings)
 {
@@
   const std::size_t size = data_models.size();
+  if (size == 0) { return {{}, 0.0}; }

Also applies to: 251-269

🤖 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/pdlp/utilities/cython_solve.cu` around lines 203 - 233, In
compute_max_thread, guard the division by needed_memory by checking for an empty
batch or zero needed_memory before computing total_mem / needed_memory: if
data_models.empty() or needed_memory == 0, return a safe default (e.g., 1) or
handle as an empty-batch case instead of performing the division; update the
same pattern in the other similar block referenced around lines 251-269 (the
equivalent function/logic) so both places validate needed_memory > 0 (and/or
data_models not empty) prior to doing total_mem / needed_memory and before
calling cuopt_expects.
🤖 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.

Outside diff comments:
In `@cpp/include/cuopt/linear_programming/solve.hpp`:
- Around line 61-63: Doxygen `@param` entries for the functions in solve.hpp
incorrectly reference the legacy optimization_problem_t<i_t,f_t>; update the
parameter descriptions to match the current type
cuopt::linear_programming::io::mps_data_model_t and its semantics: change the
description for mps_data_model to state it is a
cuopt::linear_programming::io::mps_data_model_t representing the LP problem
(including any expected ownership/constness and whether it contains constraints,
objective, and variable metadata), and ensure handle_ptr doc refers to
raft::handle_t with its CUDA stream; apply the same replacement to all affected
docblocks (the occurrences around the symbols/params mps_data_model and
handle_ptr at the other comment locations).

In `@cpp/src/pdlp/utilities/cython_solve.cu`:
- Around line 203-233: In compute_max_thread, guard the division by
needed_memory by checking for an empty batch or zero needed_memory before
computing total_mem / needed_memory: if data_models.empty() or needed_memory ==
0, return a safe default (e.g., 1) or handle as an empty-batch case instead of
performing the division; update the same pattern in the other similar block
referenced around lines 251-269 (the equivalent function/logic) so both places
validate needed_memory > 0 (and/or data_models not empty) prior to doing
total_mem / needed_memory and before calling cuopt_expects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 794733c2-84e9-42d9-86a3-80cc9b4af474

📥 Commits

Reviewing files that changed from the base of the PR and between 76257b4 and 2481589.

📒 Files selected for processing (68)
  • benchmarks/linear_programming/cuopt/benchmark_helper.hpp
  • benchmarks/linear_programming/cuopt/initial_problem_check.hpp
  • benchmarks/linear_programming/cuopt/run_mip.cpp
  • benchmarks/linear_programming/cuopt/run_pdlp.cu
  • cpp/CMakeLists.txt
  • cpp/cuopt_cli.cpp
  • cpp/include/cuopt/linear_programming/io/data_model_view.hpp
  • cpp/include/cuopt/linear_programming/io/mps_data_model.hpp
  • cpp/include/cuopt/linear_programming/io/mps_writer.hpp
  • cpp/include/cuopt/linear_programming/io/parser.hpp
  • cpp/include/cuopt/linear_programming/io/utilities/cython_mps_parser.hpp
  • cpp/include/cuopt/linear_programming/io/writer.hpp
  • cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
  • cpp/include/cuopt/linear_programming/solve.hpp
  • cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp
  • cpp/src/CMakeLists.txt
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/src/io/CMakeLists.txt
  • cpp/src/io/data_model_view.cpp
  • cpp/src/io/mps_data_model.cpp
  • cpp/src/io/mps_parser.cpp
  • cpp/src/io/mps_parser_internal.hpp
  • cpp/src/io/mps_writer.cpp
  • cpp/src/io/parser.cpp
  • cpp/src/io/utilities/cython_mps_parser.cpp
  • cpp/src/io/utilities/error.hpp
  • cpp/src/io/writer.cpp
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/pdlp/cpu_optimization_problem.cpp
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/src/pdlp/optimization_problem.cu
  • cpp/src/pdlp/solve.cu
  • cpp/src/pdlp/solve.cuh
  • cpp/src/pdlp/utilities/cython_solve.cu
  • cpp/tests/CMakeLists.txt
  • cpp/tests/dual_simplex/unit_tests/solve.cpp
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • cpp/tests/linear_programming/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • cpp/tests/linear_programming/mps_parser_test.cpp
  • cpp/tests/linear_programming/pdlp_test.cu
  • cpp/tests/linear_programming/unit_tests/optimization_problem_test.cu
  • cpp/tests/linear_programming/unit_tests/presolve_test.cu
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
  • cpp/tests/mip/bounds_standardization_test.cu
  • cpp/tests/mip/cuts_test.cu
  • cpp/tests/mip/determinism_test.cu
  • cpp/tests/mip/doc_example_test.cu
  • cpp/tests/mip/elim_var_remap_test.cu
  • cpp/tests/mip/feasibility_jump_tests.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/tests/mip/integer_with_real_bounds.cu
  • cpp/tests/mip/load_balancing_test.cu
  • cpp/tests/mip/mip_utils.cuh
  • cpp/tests/mip/miplib_test.cu
  • cpp/tests/mip/multi_probe_test.cu
  • cpp/tests/mip/presolve_test.cu
  • cpp/tests/mip/problem_test.cu
  • cpp/tests/mip/semi_continuous_test.cu
  • cpp/tests/mip/server_test.cu
  • cpp/tests/mip/termination_test.cu
  • cpp/tests/mip/unit_test.cu
  • cpp/tests/qp/unit_tests/no_constraints.cu
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • cpp/tests/utilities/inline_mps_test_utils.hpp
  • python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.pxd
✅ Files skipped from review due to trivial changes (9)
  • cpp/tests/mip/problem_test.cu
  • cpp/src/io/utilities/cython_mps_parser.cpp
  • cpp/src/CMakeLists.txt
  • cpp/tests/dual_simplex/unit_tests/solve.cpp
  • cpp/tests/mip/semi_continuous_test.cu
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • cpp/tests/mip/integer_with_real_bounds.cu
  • cpp/tests/mip/termination_test.cu
  • cpp/tests/mip/incumbent_callback_test.cu
🚧 Files skipped from review as they are similar to previous changes (16)
  • benchmarks/linear_programming/cuopt/run_mip.cpp
  • cpp/cuopt_cli.cpp
  • cpp/src/pdlp/solve.cuh
  • benchmarks/linear_programming/cuopt/run_pdlp.cu
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/tests/mip/load_balancing_test.cu
  • cpp/tests/mip/mip_utils.cuh
  • cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • cpp/tests/CMakeLists.txt
  • cpp/src/pdlp/cpu_optimization_problem.cpp
  • cpp/tests/mip/feasibility_jump_tests.cu
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • cpp/tests/mip/elim_var_remap_test.cu
  • cpp/tests/mip/presolve_test.cu
  • cpp/tests/linear_programming/CMakeLists.txt

mlubin and others added 3 commits May 11, 2026 16:20
Merge the standalone libmps_parser C++ library and the cuopt-mps-parser
Python wheel into the main cuopt build, eliminating the separate
distribution while keeping all functionality. The parser sources now live
under cpp/src/parsers/ and the public headers under
cpp/include/cuopt/linear_programming/parsers/, matching the new namespace
cuopt::linear_programming::parsers.

C++:
- Move cpp/libmps_parser/{src,include,tests}/ into the cuopt tree.
- Drop the separate libmps_parser.so target; parser sources compile
  into libcuopt.so via cpp/src/parsers/CMakeLists.txt.
- Hoist BZip2/ZLIB find_package + CUOPT_PARSER_WITH_{BZIP2,ZLIB} options
  to the top-level cpp build.
- Rename namespace cuopt::mps_parser -> cuopt::linear_programming::parsers
  across all sources, headers, tests, and benchmarks.
- Wire the gtest suite (mps_parser_test.cpp) into
  cpp/tests/linear_programming/CMakeLists.txt.

Python:
- Move python/cuopt/cuopt/linear_programming/cuopt_mps_parser/ into
  python/cuopt/cuopt/linear_programming/mps_parser/, importable as
  cuopt.linear_programming.mps_parser (and re-exported as ParseMps/toDict
  at the linear_programming package level).
- Fold data_model/ cython binding into the main cuopt wheel; relink
  data_model and solver cython modules to cuopt::cuopt.
- Update cython .pxd extern paths and namespace strings to
  cuopt/linear_programming/parsers/ and cuopt::linear_programming::parsers.
- Delete the standalone parser-wheel scaffolding (pyproject.toml,
  CMakeLists.txt, README, LICENSE, _version.py).
- Update test imports across test_parser.py, test_lp_solver.py,
  test_incumbent_callbacks.py, test_cpu_only_execution.py,
  test_pdlp_warmstart.py, plus cuopt_self_host_client.py,
  problem.py, and regression/benchmark_scripts/utils.py.

Build / packaging / CI:
- Drop the libmps_parser target and the FIND_MPS_PARSER_CPP flag from
  build.sh; help text and default action updated accordingly.
- Delete conda/recipes/mps-parser/ and the libmps-parser output (plus
  all pin_subpackage references) in conda/recipes/libcuopt/recipe.yaml.
- Drop cuopt-mps-parser =${version} from conda/recipes/cuopt/recipe.yaml.
- Remove the wheel-build-cuopt-mps-parser job, mps_parser_filter, and
  ci/build_wheel_cuopt_mps_parser.sh from CI; clean up cuopt_mps_parser
  wheelhouse downloads/installs from the remaining ci/*.sh scripts.
- Drop cuopt-mps-parser dependency entries from python/{cuopt,libcuopt,
  cuopt_self_hosted}/pyproject.toml and dependencies.yaml; remove the
  py_*_cuopt_mps_parser pyproject groups and depends_on_mps_parser.
- Update Dockerfile and ci/release/update-version.sh package lists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Miles Lubin <mlubin@nvidia.com>
Update docs, READMEs, contributor guidance, agent skills, and license
attribution to reflect the folded layout: parser sources under
cpp/src/parsers/ inside libcuopt.so, and Python parser under
cuopt.linear_programming.mps_parser inside the cuopt wheel.

- docs/cuopt/source/hidden/mps-{api,example}.rst: switch the autofunction
  target and example to cuopt.linear_programming.mps_parser.
- docs/cuopt/source/cuopt-server/examples/lp/examples/mps_datamodel_example.py:
  update the import + ParseMps call.
- cpp/README.md: rewrite the MPS parser section now that it's compiled
  into libcuopt.
- CONTRIBUTING.md: drop libmps_parser / cuopt_mps_parser from the
  build.sh examples.
- cpp/include/cuopt/linear_programming/parsers/writer.hpp: drop the
  obsolete TODO referencing the standalone libmps_parser.
- skills/cuopt-developer/{SKILL.md,resources/python_bindings.md}: refresh
  example build command and CMakeLists snippet.
- thirdparty/THIRD_PARTY_LICENSES: bzip2 and zlib usage attribution moves
  from libmps_parser to cuopt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Miles Lubin <mlubin@nvidia.com>
The directory holds both read and write paths for the MPS/QPS format
(parser + writer + data model), so "io" describes it more accurately
than "parsers". Per PR review feedback.

- cpp/include/cuopt/linear_programming/parsers/ -> .../io/
- cpp/src/parsers/ -> cpp/src/io/
- Namespace cuopt::linear_programming::parsers ->
  cuopt::linear_programming::io across all sources, headers, tests,
  benchmarks, and the cython .pxd extern declarations.
- CMake: add_subdirectory(parsers) and src/parsers include paths
  updated to io.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Miles Lubin <mlubin@nvidia.com>
@mlubin mlubin force-pushed the mps-parser-fold branch from 2481589 to c2458da Compare May 11, 2026 16:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
cpp/tests/linear_programming/mps_parser_test.cpp (1)

27-27: ⚡ Quick win

Move test-local helpers to an unnamed namespace.

At Line 27, this file enters cuopt::linear_programming::io and defines helper functions with external linkage. Please wrap test-only helpers (read_from_mps, file_exists, compare_data_models) in namespace { ... } (optionally nested inside the cuopt::linear_programming::io block) to avoid symbol leakage and potential ODR collisions.

As per coding guidelines, "**/*.{hpp,cpp,cuh,cu,h}: ... use unnamed namespaces for internal linkage."

Also applies to: 1401-1401

🤖 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/tests/linear_programming/mps_parser_test.cpp` at line 27, The helper
functions read_from_mps, file_exists, and compare_data_models are currently
defined with external linkage inside namespace cuopt::linear_programming::io;
wrap their definitions in an unnamed namespace (namespace { ... }) so they get
internal linkage (you can place the unnamed namespace inside the existing
cuopt::linear_programming::io block or around the helper definitions) to prevent
symbol leakage and ODR collisions, updating any references in the test file
accordingly.
🤖 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.

Nitpick comments:
In `@cpp/tests/linear_programming/mps_parser_test.cpp`:
- Line 27: The helper functions read_from_mps, file_exists, and
compare_data_models are currently defined with external linkage inside namespace
cuopt::linear_programming::io; wrap their definitions in an unnamed namespace
(namespace { ... }) so they get internal linkage (you can place the unnamed
namespace inside the existing cuopt::linear_programming::io block or around the
helper definitions) to prevent symbol leakage and ODR collisions, updating any
references in the test file accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5f560a1b-8531-4e4e-9ae9-a7c0d46e9975

📥 Commits

Reviewing files that changed from the base of the PR and between 2481589 and c2458da.

⛔ Files ignored due to path filters (1)
  • thirdparty/THIRD_PARTY_LICENSES is excluded by !thirdparty/**
📒 Files selected for processing (127)
  • .github/workflows/build.yaml
  • .github/workflows/pr.yaml
  • CONTRIBUTING.md
  • benchmarks/linear_programming/cuopt/benchmark_helper.hpp
  • benchmarks/linear_programming/cuopt/initial_problem_check.hpp
  • benchmarks/linear_programming/cuopt/run_mip.cpp
  • benchmarks/linear_programming/cuopt/run_pdlp.cu
  • build.sh
  • ci/build_python.sh
  • ci/build_wheel_cuopt.sh
  • ci/build_wheel_cuopt_mps_parser.sh
  • ci/build_wheel_libcuopt.sh
  • ci/docker/Dockerfile
  • ci/release/update-version.sh
  • ci/test_self_hosted_service.sh
  • ci/test_wheel_cuopt.sh
  • ci/test_wheel_cuopt_server.sh
  • conda/recipes/cuopt/recipe.yaml
  • conda/recipes/libcuopt/recipe.yaml
  • conda/recipes/mps-parser/conda_build_config.yaml
  • conda/recipes/mps-parser/recipe.yaml
  • cpp/CMakeLists.txt
  • cpp/README.md
  • cpp/cuopt_cli.cpp
  • cpp/include/cuopt/linear_programming/io/data_model_view.hpp
  • cpp/include/cuopt/linear_programming/io/mps_data_model.hpp
  • cpp/include/cuopt/linear_programming/io/mps_writer.hpp
  • cpp/include/cuopt/linear_programming/io/parser.hpp
  • cpp/include/cuopt/linear_programming/io/utilities/cython_mps_parser.hpp
  • cpp/include/cuopt/linear_programming/io/writer.hpp
  • cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
  • cpp/include/cuopt/linear_programming/solve.hpp
  • cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp
  • cpp/libmps_parser/CMakeLists.txt
  • cpp/libmps_parser/cmake/thirdparty/get_gtest.cmake
  • cpp/libmps_parser/src/utilities/cython_mps_parser.cpp
  • cpp/libmps_parser/tests/CMakeLists.txt
  • cpp/libmps_parser/tests/utilities/common_utils.hpp
  • cpp/src/CMakeLists.txt
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/src/io/CMakeLists.txt
  • cpp/src/io/data_model_view.cpp
  • cpp/src/io/mps_data_model.cpp
  • cpp/src/io/mps_parser.cpp
  • cpp/src/io/mps_parser_internal.hpp
  • cpp/src/io/mps_writer.cpp
  • cpp/src/io/parser.cpp
  • cpp/src/io/utilities/cython_mps_parser.cpp
  • cpp/src/io/utilities/error.hpp
  • cpp/src/io/writer.cpp
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/pdlp/cpu_optimization_problem.cpp
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/src/pdlp/optimization_problem.cu
  • cpp/src/pdlp/solve.cu
  • cpp/src/pdlp/solve.cuh
  • cpp/src/pdlp/utilities/cython_solve.cu
  • cpp/tests/CMakeLists.txt
  • cpp/tests/dual_simplex/unit_tests/solve.cpp
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • cpp/tests/linear_programming/CMakeLists.txt
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • cpp/tests/linear_programming/mps_parser_test.cpp
  • cpp/tests/linear_programming/pdlp_test.cu
  • cpp/tests/linear_programming/unit_tests/optimization_problem_test.cu
  • cpp/tests/linear_programming/unit_tests/presolve_test.cu
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
  • cpp/tests/mip/bounds_standardization_test.cu
  • cpp/tests/mip/cuts_test.cu
  • cpp/tests/mip/determinism_test.cu
  • cpp/tests/mip/doc_example_test.cu
  • cpp/tests/mip/elim_var_remap_test.cu
  • cpp/tests/mip/feasibility_jump_tests.cu
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/tests/mip/integer_with_real_bounds.cu
  • cpp/tests/mip/load_balancing_test.cu
  • cpp/tests/mip/mip_utils.cuh
  • cpp/tests/mip/miplib_test.cu
  • cpp/tests/mip/multi_probe_test.cu
  • cpp/tests/mip/presolve_test.cu
  • cpp/tests/mip/problem_test.cu
  • cpp/tests/mip/semi_continuous_test.cu
  • cpp/tests/mip/server_test.cu
  • cpp/tests/mip/termination_test.cu
  • cpp/tests/mip/unit_test.cu
  • cpp/tests/qp/unit_tests/no_constraints.cu
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • cpp/tests/utilities/inline_mps_test_utils.hpp
  • dependencies.yaml
  • docs/cuopt/source/cuopt-server/examples/lp/examples/mps_datamodel_example.py
  • docs/cuopt/source/hidden/mps-api.rst
  • docs/cuopt/source/hidden/mps-example.rst
  • python/cuopt/CMakeLists.txt
  • python/cuopt/cuopt/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/LICENSE
  • python/cuopt/cuopt/linear_programming/README.md
  • python/cuopt/cuopt/linear_programming/__init__.py
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/VERSION
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/__init__.py
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/_version.py
  • python/cuopt/cuopt/linear_programming/data_model/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
  • python/cuopt/cuopt/linear_programming/mps_parser/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/mps_parser/__init__.py
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.pxd
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.py
  • python/cuopt/cuopt/linear_programming/mps_parser/parser_wrapper.pyx
  • python/cuopt/cuopt/linear_programming/mps_parser/utilities/__init__.py
  • python/cuopt/cuopt/linear_programming/mps_parser/utilities/exception_handler.py
  • python/cuopt/cuopt/linear_programming/problem.py
  • python/cuopt/cuopt/linear_programming/pyproject.toml
  • python/cuopt/cuopt/linear_programming/solver/CMakeLists.txt
  • python/cuopt/cuopt/linear_programming/solver/solver.py
  • python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py
  • python/cuopt/cuopt/tests/linear_programming/test_incumbent_callbacks.py
  • python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
  • python/cuopt/cuopt/tests/linear_programming/test_parser.py
  • python/cuopt/pyproject.toml
  • python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
  • python/cuopt_self_hosted/pyproject.toml
  • python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
  • python/libcuopt/pyproject.toml
  • regression/benchmark_scripts/utils.py
  • skills/cuopt-developer/resources/build_and_test.md
  • skills/cuopt-developer/resources/python_bindings.md
💤 Files with no reviewable changes (28)
  • python/cuopt/cuopt/linear_programming/LICENSE
  • cpp/libmps_parser/src/utilities/cython_mps_parser.cpp
  • cpp/libmps_parser/cmake/thirdparty/get_gtest.cmake
  • python/cuopt/CMakeLists.txt
  • python/cuopt_self_hosted/pyproject.toml
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/VERSION
  • python/cuopt/pyproject.toml
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/init.py
  • ci/test_wheel_cuopt_server.sh
  • python/cuopt/cuopt/linear_programming/README.md
  • conda/recipes/mps-parser/recipe.yaml
  • ci/release/update-version.sh
  • conda/recipes/mps-parser/conda_build_config.yaml
  • cpp/README.md
  • python/cuopt/cuopt/linear_programming/cuopt_mps_parser/_version.py
  • ci/build_wheel_cuopt_mps_parser.sh
  • ci/build_wheel_libcuopt.sh
  • ci/test_self_hosted_service.sh
  • ci/docker/Dockerfile
  • conda/recipes/cuopt/recipe.yaml
  • ci/test_wheel_cuopt.sh
  • python/cuopt/cuopt/linear_programming/CMakeLists.txt
  • cpp/libmps_parser/tests/CMakeLists.txt
  • python/libcuopt/pyproject.toml
  • python/cuopt/cuopt/linear_programming/pyproject.toml
  • cpp/libmps_parser/CMakeLists.txt
  • cpp/libmps_parser/tests/utilities/common_utils.hpp
  • dependencies.yaml
✅ Files skipped from review due to trivial changes (24)
  • docs/cuopt/source/hidden/mps-api.rst
  • docs/cuopt/source/hidden/mps-example.rst
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • cpp/tests/mip/semi_continuous_test.cu
  • cpp/tests/mip/feasibility_jump_tests.cu
  • cpp/src/CMakeLists.txt
  • cpp/src/io/utilities/cython_mps_parser.cpp
  • python/cuopt/cuopt/linear_programming/mps_parser/init.py
  • skills/cuopt-developer/resources/python_bindings.md
  • cpp/tests/mip/problem_test.cu
  • cpp/tests/mip/termination_test.cu
  • cpp/src/io/utilities/error.hpp
  • python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
  • cpp/include/cuopt/linear_programming/io/parser.hpp
  • cpp/tests/qp/unit_tests/no_constraints.cu
  • python/cuopt/cuopt/tests/linear_programming/test_incumbent_callbacks.py
  • python/cuopt/cuopt/linear_programming/solver/solver.py
  • python/cuopt/cuopt/tests/linear_programming/test_parser.py
  • cpp/src/io/CMakeLists.txt
  • python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
  • python/cuopt/cuopt/linear_programming/init.py
  • python/cuopt/cuopt/CMakeLists.txt
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • cpp/tests/mip/integer_with_real_bounds.cu
🚧 Files skipped from review as they are similar to previous changes (61)
  • cpp/include/cuopt/linear_programming/io/mps_writer.hpp
  • cpp/src/io/mps_parser_internal.hpp
  • python/cuopt/cuopt/linear_programming/data_model/CMakeLists.txt
  • benchmarks/linear_programming/cuopt/initial_problem_check.hpp
  • python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
  • cpp/tests/mip/determinism_test.cu
  • cpp/tests/dual_simplex/unit_tests/solve.cpp
  • cpp/tests/mip/incumbent_callback_test.cu
  • cpp/src/branch_and_bound/pseudo_costs.cpp
  • cpp/tests/utilities/inline_mps_test_utils.hpp
  • cpp/tests/linear_programming/unit_tests/optimization_problem_test.cu
  • benchmarks/linear_programming/cuopt/run_pdlp.cu
  • cpp/src/io/writer.cpp
  • benchmarks/linear_programming/cuopt/benchmark_helper.hpp
  • python/cuopt/cuopt/linear_programming/mps_parser/utilities/init.py
  • cpp/tests/mip/bounds_standardization_test.cu
  • cpp/include/cuopt/linear_programming/io/data_model_view.hpp
  • docs/cuopt/source/cuopt-server/examples/lp/examples/mps_datamodel_example.py
  • python/cuopt/cuopt/linear_programming/solver/CMakeLists.txt
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • python/cuopt/cuopt/linear_programming/mps_parser/parser.py
  • cpp/include/cuopt/linear_programming/io/utilities/cython_mps_parser.hpp
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/pdlp/optimization_problem.cu
  • cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
  • cpp/tests/linear_programming/CMakeLists.txt
  • cpp/src/pdlp/cpu_optimization_problem.cpp
  • cpp/src/io/parser.cpp
  • cpp/src/io/mps_writer.cpp
  • cpp/tests/CMakeLists.txt
  • cpp/src/io/mps_parser.cpp
  • cpp/include/cuopt/linear_programming/utilities/cython_solve.hpp
  • CONTRIBUTING.md
  • python/cuopt/cuopt/linear_programming/mps_parser/parser_wrapper.pyx
  • ci/build_wheel_cuopt.sh
  • cpp/tests/mip/mip_utils.cuh
  • python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py
  • cpp/src/io/data_model_view.cpp
  • python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
  • cpp/tests/linear_programming/unit_tests/presolve_test.cu
  • cpp/tests/mip/load_balancing_test.cu
  • conda/recipes/libcuopt/recipe.yaml
  • cpp/tests/linear_programming/utilities/pdlp_test_utilities.cuh
  • cpp/tests/mip/cuts_test.cu
  • cpp/src/pdlp/cuopt_c.cpp
  • cpp/include/cuopt/linear_programming/solve.hpp
  • cpp/tests/linear_programming/grpc/grpc_integration_test.cpp
  • cpp/tests/mip/doc_example_test.cu
  • cpp/tests/mip/miplib_test.cu
  • cpp/src/pdlp/solve.cu
  • python/cuopt/cuopt/linear_programming/problem.py
  • python/cuopt/cuopt/linear_programming/mps_parser/CMakeLists.txt
  • cpp/include/cuopt/linear_programming/io/writer.hpp
  • cpp/CMakeLists.txt
  • regression/benchmark_scripts/utils.py
  • cpp/tests/mip/unit_test.cu
  • cpp/src/pdlp/utilities/cython_solve.cu
  • cpp/src/io/mps_data_model.cpp
  • build.sh
  • .github/workflows/pr.yaml
  • ci/build_python.sh

@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 11, 2026

/ok to test c2458da

After folding the cuopt-mps-parser wheel into cuopt, the top-level
`data_model` Python module no longer exists. Update the sh-cli-api.rst
autoclass directive to point at the new path
`cuopt.linear_programming.data_model.DataModel`.

This unblocks the docs-build CI job, which treats sphinx autodoc
import failures as errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Miles Lubin <mlubin@nvidia.com>
@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 11, 2026

/ok to test 777ccdd

@mlubin mlubin force-pushed the mps-parser-fold branch from c36c9a0 to 777ccdd Compare May 11, 2026 20:14
The nitpick_ignore entry suppressing the auto-generated xref for the
inherited set_data_model_view method was keyed on the top-level
`data_model.DataModel` path that the now-removed cuopt-mps-parser wheel
used. After folding the parser into cuopt, the autoclass directive in
sh-cli-api.rst points at `cuopt.linear_programming.data_model.DataModel`,
so update the ignore entry's prefix to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Miles Lubin <mlubin@nvidia.com>
@mlubin
Copy link
Copy Markdown
Contributor Author

mlubin commented May 11, 2026

/ok to test dd4abd5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Introduces a breaking change improvement Improves an existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants