Skip to content

Java bindings for LP/MIP/QP#1524

Open
nvidiacbrissette wants to merge 5 commits into
NVIDIA:mainfrom
nvidiacbrissette:cbrissette/cuopt-bindings
Open

Java bindings for LP/MIP/QP#1524
nvidiacbrissette wants to merge 5 commits into
NVIDIA:mainfrom
nvidiacbrissette:cbrissette/cuopt-bindings

Conversation

@nvidiacbrissette

Copy link
Copy Markdown

Description

Added Java bindings at parity with LP/MIP/QP along with some basic tests for functionality. Currently it is fully decoupled from other build components and must be built on its own.

Checklist

@nvidiacbrissette nvidiacbrissette requested review from a team as code owners July 7, 2026 19:34
@nvidiacbrissette nvidiacbrissette requested a review from tmckayus July 7, 2026 19:34
@copy-pr-bot

copy-pr-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new standalone Java module (java/cuopt) implementing JNI-based bindings for cuOpt LP, MILP, and QP solving. It adds Java modeling classes (Problem, Variable, Constraint, expressions), native C++/JNI glue code, CMake/Maven build tooling, CI workflow wiring, tests, and Sphinx documentation.

Changes

Java JNI Bindings Implementation

Layer / File(s) Summary
Core enums, exceptions, primitives
.../ConstraintSense.java, .../ObjectiveSense.java, .../PDLPSolverMode.java, .../ProblemCategory.java, .../SolverMethod.java, .../TerminationStatus.java, .../VariableType.java, .../CSRMatrix.java, .../CuOptException.java, .../Variable.java
Defines native-value-backed enums, an immutable CSR matrix holder, a status-code exception, and the Variable model class with bounds/type/name and result fields.
Linear/quadratic expressions and constraints
.../ObjectiveExpression.java, .../LinearExpression.java, .../QuadraticExpression.java, .../Constraint.java, .../QuadraticConstraint.java
Implements fluent arithmetic, comparison builders (le/ge/eq), coefficient tracking, and slack/value computation for objectives and constraints.
Native declarations and callbacks
.../NativeCuOpt.java, .../MIPCallback.java, .../MIPCallbackSolution.java, .../MIPSetSolutionCallback.java, .../MIPSolutionCallback.java
Declares native JNI methods and callback interfaces used to drive the native optimizer and receive MIP incumbents.
Native C++/JNI implementation
.../cuopt_jni.cpp, .../cuopt_java_native_api.cpp, .../cuopt_java_native_api.hpp
Implements JNI entrypoints for problem/settings/solution lifecycle, quadratic modeling, MIP callback routing, and LP/MIP statistics.
SolverSettings, Solution, and stats
.../SolverSettings.java, .../Solution.java, .../LPStats.java, .../MIPStats.java
Wraps native settings/solution handles with typed getters/setters, MIP callback registration, dump/load, and LP/MIP statistics accessors, all with Cleaner-based cleanup.
NativeProblem wrapper
.../NativeProblem.java
Wraps the native problem handle with factories, quadratic objective/constraint management, solving, and MPS I/O.
Problem high-level API
.../Problem.java
Provides variable/constraint/objective management, solve orchestration, MPS read/write, update/relax, and incumbent/warm-start accessors.
Build tooling
java/cuopt/CMakeLists.txt, pom.xml, scripts/*.sh, README.md, .gitignore
Adds CMake configuration for the JNI library, Maven build/test setup, and helper shell scripts for native build/test/constant generation.
CI/CD wiring
.github/workflows/*.yaml, ci/build_java.sh, ci/test_java.sh, ci/release/update-version.sh, dependencies.yaml
Adds Java build/test jobs, changed-files detection, and dependency/version management for the Java module.
Tests
.../ProblemModelingTest.java, .../NativeIntegrationTest.java, .../ProblemIntegrationTest.java, TESTS.md
Adds pure-modeling, native/JNI integration, and end-to-end problem tests, with documentation of coverage and run instructions.
Documentation (RST)
docs/cuopt/source/cuopt-java/**, docs/cuopt/source/index.rst
Adds Sphinx pages for the Java API landing page, quick-start, convex (LP/QP) API/examples, and MIP API/examples.

Estimated code review effort: 4 (Complex) | ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: new Java bindings for LP/MIP/QP functionality.
Description check ✅ Passed The description is aligned with the changeset, covering Java bindings, tests, documentation, and standalone build intent.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@mlubin mlubin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I saw the PR is closed, sending my comments as I had them already written up.

import java.util.List;

/** Compatibility entry point for Python's deprecated LP BatchSolve API. */
public final class BatchSolve {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need to add a java wrapper for the API that we're going to delete.


extern "C" {

cuopt_int_t cuOptLoadParametersFromFile(cuOptSolverSettings settings, const char* path);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should discuss merging these extensions into the C API.


import java.util.Arrays;

public final class PDLPWarmStartData {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are we exposing PDLPWarmStartData? I think this is internal.

@@ -0,0 +1,1367 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd recommend avoiding a test dependency on the python interface. The java interface should stand on its own.

Comment thread java/cuopt/README.md Outdated
@@ -0,0 +1,25 @@
# cuOpt Java bindings (beta)

This directory is an isolated, customer-specific beta module for the cuOpt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this how we want to ship it?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We would want to follow cuvs and try to publish to maven https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java

===================================

The Java LP/QP bindings are in the package
``com.nvidia.cuopt.linearprogramming``. The public API is documented below by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use com.nvidia.cuopt.mathematicalprogramming (or mathematical_programming if java conventions allow). The linear_programming name is deprecated. We changed it on the C++ side and someday will change it on the python side.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1 to this comment

@nvidiacbrissette

Copy link
Copy Markdown
Author

Sorry that was an accident. Reopening.

@@ -0,0 +1,28 @@
/home/cbrissette/cuopt/java/cuopt/src/main/java/com/nvidia/cuopt/linearprogramming/BatchSolve.java

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need these files ? may be we can delete all the run time files so developers can concentrate on main parts.

@ramakrishnap-nv ramakrishnap-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Focused review on APIs and shipping (vs how cuvs ships Java).

APIs: the surface is broad and, pleasingly, closely in sync with the Python API — the algebraic Problem layer matches Python's (camelCase) modeling methods almost 1:1, and DataModel maps cleanly (snake_case→camelCase). A few parity gaps and Java-idiom nits are noted inline.

Shipping: the main blockers — don't commit target/, and wire the build into CI/release the way cuvs does (ci/build_java.sh/ci/test_java.sh, dependencies.yaml java key, workflow jobs, version marker, docs toctree).

Non-blocking review comments below.

@@ -0,0 +1,34 @@
com/nvidia/cuopt/linearprogramming/SolverSettings$NativeHandle.class

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Build output committed. The whole java/cuopt/target/ tree (~51 files: .class, surefire-reports/, maven-status/, generated-sources/) is Maven output and shouldn't be in git. Please remove it and add a java/.gitignore (target/, *.iml, hs_err*.log), as cuvs does.

@@ -0,0 +1,65 @@
#!/usr/bin/env bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not wired into CI/release — this is the "must be built on its own" gap. Consider ci/build_java.sh + ci/test_java.sh that pull the C++ artifact via rapids-download-from-github "$(rapids-artifact-name conda_cpp libcuopt cuopt --cuda "$RAPIDS_CUDA_VERSION")", plus java-build/conda-java-tests jobs in pr.yaml/test.yaml and a java file-key in dependencies.yaml — mirroring how cuvs ships Java.

Comment thread java/cuopt/pom.xml Outdated

<groupId>com.nvidia</groupId>
<artifactId>cuopt</artifactId>
<version>26.8.0-SNAPSHOT</version>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use the RAPIDS version format 26.08.00 (not 26.8.0-SNAPSHOT) and add a version-update marker (cuvs: <!--CUVS_JAVA#VERSION_UPDATE_MARKER_START-->...<!--...END-->) so ci/release/update-version.sh bumps it. Also consider groupId com.nvidia.cuopt.

@@ -0,0 +1,42 @@
====================================

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These Java docs aren't referenced by any Sphinx toctree, so they won't render. Please add cuopt-java/index to the top-level docs toctree.

}

/** Return true for maximize and false for minimize, matching Python get_sense(). */
public boolean getSense() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Parity note (not a rename request): getSense() correctly matches the Python DataModel.get_sense() (bool, True=maximize) — good. Two parity gaps vs Python though: (1) Python puts set_initial_primal_solution/set_initial_dual_solution on DataModel, whereas here they're on SolverSettings; (2) Python DataModel also exposes getters this class seems to lack: get_quadratic_objective_{values,indices,offsets}, get_variable_names/get_row_names, get_objective_name/get_problem_name, get_ascii_row_types.

resetSolvedValues();
}

public Object getObjective() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

getObjective() returns Object — callers must downcast. Prefer a typed return (or overloads). Same for SolverSettings.getTypedParameter() / getMipCallbacks(). (The modeling API otherwise tracks the Python Problem layer 1:1 — nice.)

private final int[] columnIndices;
private final double[] values;

public CsrMatrix(int[] rowOffsets, int[] columnIndices, double[] values) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Arg order is (rowOffsets, columnIndices, values), but DataModel.setCsrConstraintMatrix is (values, indices, offsets) (matching Python's set_csr_constraint_matrix(A_values, A_indices, A_offsets)). This class is the odd one out — reversing it here is easy to transpose silently; consider aligning.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Python's order seems wrong though. The order should be rowOffset, columnIndices, values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe we should fix Python?

High-level model
----------------

``Problem`` is the recommended entry point for models built in Java.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's confusing to mix the terms model and Problem. I would use the term problem throughout and remove references to model

- Set a quadratic objective with optional linear and constant terms.
* - ``solve()`` / ``solve(SolverSettings)``
- Convert the model to a native ``DataModel`` and return a ``Solution``.
* - ``toDataModel()``

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it necessary to have a DataModel? I think we should deprecate this.


The model also exposes ``getVariables``, ``getVariable``, ``getConstraints``,
``getConstraint``, ``getNumVariables``, ``getNumConstraints``,
``getNumNonZeros``, ``isMip``, ``isSolved``, ``getStatus``,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should have consistent capitalization. Here we use isMip, but elsewhere we have MIPStats. I personally think we should always use MIP, LP and QP and cuOpt, even if it breaks Java convention. But failing that we should at least be consistent. Same for MPS and CSR


``SolverSettings`` owns native solver configuration and implements
``AutoCloseable``. Parameters can be set with the overloaded
``setParameter`` methods for ``String``, ``int``, ``double``, and ``boolean``

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here we are mixing the terms Settings and Parameters. I suggest we use Setting throughout for consistency.

* primal and dual initial solutions;
* ``dumpParametersToFile`` and ``loadParametersFromFile``;
* ``toDict``;
* ``setPdlpWarmStartData``; and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Capitialization of PDLP is inconsistent: we have setPdlpWarmStartData and PDLPSolverMode.

* ``getSolveTime`` and ``getProblemCategory``; and
* ``getVars`` when variable names are available.

LP solutions additionally expose ``getLpStats`` and PDLP warm-start data.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inconsistent capitalization: getLpStats vs LPStats.

MPS, batching, and errors
-------------------------

``DataModel.read``, ``DataModel.parseMps``, ``Problem.read``, and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inconsistent capitalization: parseMps and readMPS

0.0,
new double[] {1.0, 1.0},
matrix,
new byte[] {(byte) 'G'},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it possible to do this with just 'G' instead of (byte) 'G'?

}

Only ``LE`` and ``GE`` quadratic constraints are supported. Calling
``QuadraticExpression.eq`` or adding an equality quadratic constraint raises

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should not even have a QuadraticExpression.eq

quadratic matrices, MPS I/O, and solver results.

Quadratic constraints are supported for ``LE`` and ``GE`` constraints. Equality
quadratic constraints are rejected by the Java API. Dedicated SOCP modeling

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove the sentence beginning with "Dedicated SOCP modeling ... " I'm not sure what it means and I don't think it's necessary to say this.

Comment thread docs/cuopt/source/cuopt-java/index.rst Outdated
====================================

NVIDIA cuOpt provides experimental Java bindings for linear programming (LP),
mixed-integer linear programming (MILP), quadratic programming (QP), and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about QCQP and SOCP?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Mentions added.

package com.nvidia.cuopt.linearprogramming;

public enum PDLPSolverMode {
STABLE1(0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm worried that these are going to get out of date. Is there any way that this can get the values from the c++ code? Rather than hard-coding it into the interface?

public enum ProblemCategory {
LP(0),
MIP(1),
IP(2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should deprecate IP across the whole code base.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed for Java.

package com.nvidia.cuopt.linearprogramming;

public enum SolverMethod {
CONCURRENT(0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm worried this is going to get out of date. Is there any way to get these values from the c++ code?

package com.nvidia.cuopt.linearprogramming;

public enum TerminationStatus {
NO_TERMINATION(0),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm worried this is going to get out of date. Is there anyway to get these values from the C++ code?

@@ -0,0 +1,151 @@
package com.nvidia.cuopt.linearprogramming;

public final class CuOptConstants {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this file autogenerated? If so, we should put a comment at the top saying it is autogenerated and add instructions on how to regenerate the file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is autogenerated. Instructions are now in the README.

@chris-maes chris-maes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for adding the JAVA API. Let's make sure that capitalization is consistent before merging.

@ramakrishnap-nv ramakrishnap-nv mentioned this pull request Jul 8, 2026
3 tasks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (9)
java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/DataModelIntegrationTest.java (1)

354-389: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused expectSolutionValues/expectedObjective fields for the QP case.

qp_diagonal_objective passes expectSolutionValues=true and expectedObjective=Double.NaN, but verify() returns before assertSolution() for any case hasQuadraticObjective() (lines 37-41), so these values are never checked. Minor - not a functional issue given the explanatory comment, just slightly misleading at the call site.

🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/DataModelIntegrationTest.java`
around lines 354 - 389, The qp_diagonal_objective CaseSpec is setting
expectSolutionValues and expectedObjective even though
DataModelIntegrationTest.verify() exits early for cases with
hasQuadraticObjective() before assertSolution() runs, so these values are
misleading at the call site. Update this test setup to either remove those
unused expectations from the CaseSpec builder chain or move the checks into the
quadratic-path assertions in verify()/assertSolution() using the CaseSpec and
its hasQuadraticObjective()/withQuadraticObjective() flow.
java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java (2)

20-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate test-scaffolding helpers across test classes.

assumeNativeLibrary() and assumeCudaDriverAvailable() are duplicated verbatim (with a small divergence - this file omits redirectErrorStream(true), DataModelIntegrationTest includes it) between NativeIntegrationTest and DataModelIntegrationTest. Consider extracting a shared test-support class to avoid drift between the two copies.

♻️ Suggested extraction
// e.g. NativeTestSupport.java
final class NativeTestSupport {
  static void assumeNativeLibrary() {
    String nativeDir = System.getProperty("cuopt.native.dir");
    Assumptions.assumeTrue(nativeDir != null && !nativeDir.isBlank(), "cuopt.native.dir is unset");
    Assumptions.assumeTrue(
        Files.exists(Path.of(nativeDir, System.mapLibraryName("cuopt_jni"))),
        "libcuopt_jni is not built");
  }

  static void assumeCudaDriverAvailable() {
    try {
      Process process = new ProcessBuilder("nvidia-smi").redirectErrorStream(true).start();
      Assumptions.assumeTrue(process.waitFor() == 0, "CUDA driver is unavailable");
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      Assumptions.assumeTrue(false, "CUDA driver check was interrupted");
    } catch (Exception e) {
      Assumptions.assumeTrue(false, "CUDA driver check failed: " + e.getMessage());
    }
  }
}

Also applies to: 172-178, 235-246

🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java`
around lines 20 - 26, The native test setup helpers are duplicated between
NativeIntegrationTest and DataModelIntegrationTest, so extract the shared logic
into a single test-support helper (for example, a NativeTestSupport class) and
have both tests call it. Move assumeNativeLibrary() and
assumeCudaDriverAvailable() into that shared class, keep the existing behavior
consistent across both callers, and align the ProcessBuilder setup so the two
copies do not drift (including the redirectErrorStream(true) detail where
needed).

169-184: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Quadratic objective built from an unrelated Problem's variables.

x0/x1 come from a freshly created shell Problem, not from tinyLP()'s DataModel. This only works because shell's variables happen to get indices 0/1, matching tinyLP()'s variable count/order. A comment explaining the intentional index-only usage (or building the expression against tinyLP's actual variable count) would make the contract explicit and less brittle to future edits of tinyLP().

🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java`
around lines 169 - 184, The small QP test is building the quadratic objective
from variables owned by a separate Problem, which relies on incidental matching
indices and is brittle. Update solvesSmallQP to make the intent explicit by
either constructing the expression from the DataModel/tinyLP variable context or
adding a clear comment near the Problem/addVariable and QuadraticExpression.of
usage that this is intentional index-only wiring, so future changes to tinyLP()
don’t silently break the test.
java/cuopt/scripts/build_native.sh (1)

12-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated JAVA_HOME auto-detection logic with test.sh.

This 7-line block (locate javac/java from PATH, derive JAVA_HOME, validate) is repeated almost verbatim in java/cuopt/scripts/test.sh (lines 15-25). Consider extracting into a small sourced helper (e.g. scripts/_java_home.sh) to avoid the two copies drifting.

🤖 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 `@java/cuopt/scripts/build_native.sh` around lines 12 - 23, The JAVA_HOME
auto-detection logic in build_native.sh is duplicated in test.sh and should be
shared to avoid drift. Extract the locate-derive-validate flow into a small
sourced helper script (for example, a reusable Java-home setup helper) and have
both build_native.sh and test.sh source it instead of maintaining separate
copies; keep the existing JAVAC_PATH/JAVA_HOME detection and validation behavior
centralized in that helper.
java/cuopt/CMakeLists.txt (1)

11-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider find_package(CUDAToolkit) instead of hand-rolled path resolution.

Manually locating libcudart.so and CUDA include dirs via CONDA_PREFIX/targets/<arch>-linux duplicates what find_package(CUDAToolkit) already does portably (and would fix the aarch64 path issue above for free).

🤖 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 `@java/cuopt/CMakeLists.txt` around lines 11 - 52, The CUDA runtime and include
path resolution in CMakeLists.txt is hand-rolled and should be replaced with
CUDAToolkit discovery. Update the cuopt_jni setup to use
find_package(CUDAToolkit) and then reference its imported targets/variables
instead of hardcoding CONDA_PREFIX, targets/x86_64-linux, and /usr/local/cuda
paths. Keep the existing cuOpt-specific checks, but switch target_link_libraries
and include directories to symbols from CUDAToolkit so the JNI build stays
portable across architectures.
.github/workflows/build.yaml (1)

72-95: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

GPU node requested for build-only job.

java-build runs ci/build_java.sh without --run-java-tests (build+package only, no GPU work), yet requests node_type: "gpu-l4-latest-1". If a CPU-only builder is available for the C++/CUDA-toolkit-linking step, using it here would avoid consuming GPU capacity for a job that doesn't execute GPU code.

🤖 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 @.github/workflows/build.yaml around lines 72 - 95, The java-build workflow
job is unnecessarily reserving a GPU node even though it only runs
ci/build_java.sh for build/package and does not execute Java tests or GPU work.
Update the java-build job’s node_type setting in the custom-job invocation to
use a CPU-only builder if available, while keeping the rest of the matrix and
artifact/upload configuration unchanged.
java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/LinearExpression.java (1)

96-98: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No guard against division by zero in dividedBy.

dividedBy(0.0) silently produces Infinity/NaN coefficients that propagate into the constraint matrix passed to the native solver, rather than failing fast with a clear error at model-construction time.

🛡️ Proposed guard
 public LinearExpression dividedBy(double scalar) {
+  if (scalar == 0.0) {
+    throw new IllegalArgumentException("Cannot divide expression by zero");
+  }
   return times(1.0 / scalar);
 }
🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/LinearExpression.java`
around lines 96 - 98, Add a fail-fast guard in LinearExpression.dividedBy so
scalar values of 0.0 are rejected before calling times(1.0 / scalar). Update the
LinearExpression.dividedBy method to validate the input and throw a clear
exception for zero (and any other invalid non-finite case if appropriate), so
invalid coefficients cannot propagate into the native solver.
java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/QuadraticExpression.java (1)

108-110: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Same division-by-zero gap as LinearExpression.dividedBy.

Consider applying the same guard here for consistency.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/QuadraticExpression.java`
around lines 108 - 110, The QuadraticExpression.dividedBy method has the same
missing division-by-zero guard as LinearExpression.dividedBy. Update
QuadraticExpression.dividedBy(double scalar) to validate the scalar before
calling times(1.0 / scalar), and make its behavior consistent with the
corresponding linear expression method by rejecting zero (and any other
unsupported values if applicable) with the same error handling pattern.
java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java (1)

165-216: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Public API marked @Deprecated is load-bearing for the entire solve/writeMPS path.

toDataModel() is annotated @Deprecated(since = "26.08") with "Use Problem directly", yet solve(SolverSettings) (line 226) and writeMPS(String) (line 238) both call it internally on every invocation. This is confusing: the class's own primary API depends on a method it tells external users to stop using. Consider extracting a private/package-private builder (e.g. buildDataModel()) for internal use and keeping the public toDataModel() as a thin deprecated wrapper, so the deprecation signal is accurate and internal callers don't trigger deprecation warnings.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`
around lines 165 - 216, The deprecated public toDataModel() method is still
being used as the internal implementation for solve(SolverSettings) and
writeMPS(String), which makes the deprecation message misleading. Extract the
shared conversion logic into a private/package-private builder method such as
buildDataModel(), have solve and writeMPS call that internal helper, and keep
toDataModel() as a thin deprecated wrapper so external callers still see the
deprecation while internal paths avoid depending on the deprecated API.
🤖 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 @.github/workflows/build.yaml:
- Around line 63-71: The java-build-matrix job is still hardcoded to use
pull-request, which causes the wrong Java matrix for non-PR runs. Update the
workflow input in build.yaml so the uses: compute-matrix.yaml call for
java-build-matrix passes the same build_type used by the workflow instead of a
fixed value, keeping the matrix selection aligned with the trigger type.

In `@java/cuopt/CMakeLists.txt`:
- Around line 23-24: The CUOPT CUDA runtime and include paths are hardcoded to
x86_64 and will break supported aarch64 builds. Update the
CUOPT_CUDA_RUNTIME_DIR and related include-path logic in CMakeLists.txt to
derive the target architecture dynamically, using CMAKE_SYSTEM_PROCESSOR or CUDA
toolkit discovery instead of embedding x86_64-linux, so CUOPT_CUDART_LIBRARY
resolves correctly on both x86_64 and aarch64.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/CSRMatrix.java`:
- Around line 15-22: Add explicit validation in CSRMatrix’s constructor before
calling Arrays.copyOf: reject null values, columnIndices, and rowOffsets with a
clear IllegalArgumentException, then validate CSR consistency by checking that
the rowOffsets array is non-empty, starts at 0, is monotonic, and that its last
entry matches values.length (and therefore the nnz count). Keep the existing
values/columnIndices length check, and ensure the constructor fails fast with a
descriptive message so malformed matrices cannot reach NativeCuOpt.createProblem
or setConstraintMatrix.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java`:
- Around line 253-263: The quadratic objective cache in DataModel becomes stale
when mixing setQuadraticObjectiveMatrix(...) and
setQuadraticObjective(QuadraticExpression), because the latter updates native
state but leaves quadraticObjectiveValues/Indices/Offsets and
quadraticObjectiveMatrixSet unchanged. Update setQuadraticObjective(...) to keep
the cached CSR arrays and flag in sync with the new objective, and make sure the
getter methods getQuadraticObjectiveValues/Indices/Offsets() always reflect the
most recently set objective rather than old matrix data.
- Around line 413-500: The quadratic constraint naming logic in DataModel is
using insertion order, so after read() or parseMPS() leaves
quadraticConstraintNames empty, the next addQuadraticConstraint(...) can
overwrite an existing native constraint’s name. Fix this by initializing
quadraticConstraintNames from the loaded model in the parsing/load path, or by
associating names with the native row index instead of list position; update the
addQuadraticConstraint overloads and getQuadraticConstraints so they resolve
names using a stable identifier rather than append order.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`:
- Around line 393-414: `updateConstraint` is missing the same ownership
validation that `updateObjective` uses for coefficient variables. In
`Problem.updateConstraint`, add a check in the `coefficients` loop to নিশ্চিত
each `Variable` key belongs to this `Problem`’s `variables` set and throw
`IllegalArgumentException` if not, before merging it into the
`LinearExpression`; keep the rest of the update flow in `updateConstraint`
unchanged.
- Around line 416-456: The updateObjective method in Problem is leaving the
linearObjective field and objectiveSet flag unchanged when coefficients are
provided before any objective has been initialized and constant is null, so
getObjective() stays stale. Add a branch in updateObjective that rebuilds
linearObjective from the current variable objective coefficients (or otherwise
synchronizes linearObjective) and marks objectiveSet true for this
coefficients-only path, while keeping the existing ObjectiveSense handling and
compatibility with objectiveCoefficients() and getObjective().
- Around line 39-55: `addVariable(...)` and `addConstraint(...)` only mark the
model unsolved, leaving stale solve metadata behind. Update both methods in
`Problem` to also clear cached solved state the same way `setObjective(...)`
does by invoking `resetSolvedValues()` after mutating `variables` or
`constraints`, so `getStatus()`, `getObjectiveValue()`, `getSolveTime()`, and
cached variable/constraint values no longer reflect an older solve.
- Around line 222-235: The solve(SolverSettings) flow in Problem leaves the
native Solution open if populateSolution(...) throws after dataModel.solve(...)
returns. Update this method so the Solution returned from
dataModel.solve(actualSettings) is always closed on the failure path, e.g. by
wrapping populateSolution(solution) in its own try/catch/finally and calling
solution.close() before rethrowing, while preserving the existing closeSettings
handling for SolverSettings.
- Around line 278-310: The constraint reconstruction logic in
Problem.read/Problem.readMPS is flattening ranged rows into a single LE/GE/EQ
constraint based on rhs[row], which drops the original interval bounds. Update
the row handling around the ConstraintSense selection and switch so rows with
differing constraintLowerBounds[row] and constraintUpperBounds[row] are either
preserved via a range-aware representation or explicitly rejected with a clear
error instead of being converted. Keep the existing single-sided path for
non-ranged rows and make the behavior around rowNames and addConstraint
consistent with the chosen handling.

In `@java/cuopt/src/main/native/cuopt_jni.cpp`:
- Around line 261-289: The JNI callback in mip_get_solution_callback leaks a
local reference because GetObjectClass(context->callback) is never paired with a
DeleteLocalRef. Store the jclass returned by GetObjectClass, use it for
GetMethodID, and delete that local ref before returning; keep the rest of the
callback flow in cuopt_jni.cpp unchanged.
- Around line 291-327: mip_set_solution_callback is leaking several JNI local
references and silently ignoring a solution-size mismatch. In this callback,
delete the local refs created by GetObjectClass, CallObjectMethod,
GetObjectClass on callback_solution, and GetObjectField for the solution array
before returning, ideally using a consistent cleanup path in
mip_set_solution_callback. Also add explicit handling for the values.size() !=
context->num_variables case in the same function: surface an error via the
existing JNI/error-reporting mechanism or logging instead of doing nothing, so
callback sizing bugs are diagnosable.

---

Nitpick comments:
In @.github/workflows/build.yaml:
- Around line 72-95: The java-build workflow job is unnecessarily reserving a
GPU node even though it only runs ci/build_java.sh for build/package and does
not execute Java tests or GPU work. Update the java-build job’s node_type
setting in the custom-job invocation to use a CPU-only builder if available,
while keeping the rest of the matrix and artifact/upload configuration
unchanged.

In `@java/cuopt/CMakeLists.txt`:
- Around line 11-52: The CUDA runtime and include path resolution in
CMakeLists.txt is hand-rolled and should be replaced with CUDAToolkit discovery.
Update the cuopt_jni setup to use find_package(CUDAToolkit) and then reference
its imported targets/variables instead of hardcoding CONDA_PREFIX,
targets/x86_64-linux, and /usr/local/cuda paths. Keep the existing
cuOpt-specific checks, but switch target_link_libraries and include directories
to symbols from CUDAToolkit so the JNI build stays portable across
architectures.

In `@java/cuopt/scripts/build_native.sh`:
- Around line 12-23: The JAVA_HOME auto-detection logic in build_native.sh is
duplicated in test.sh and should be shared to avoid drift. Extract the
locate-derive-validate flow into a small sourced helper script (for example, a
reusable Java-home setup helper) and have both build_native.sh and test.sh
source it instead of maintaining separate copies; keep the existing
JAVAC_PATH/JAVA_HOME detection and validation behavior centralized in that
helper.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/LinearExpression.java`:
- Around line 96-98: Add a fail-fast guard in LinearExpression.dividedBy so
scalar values of 0.0 are rejected before calling times(1.0 / scalar). Update the
LinearExpression.dividedBy method to validate the input and throw a clear
exception for zero (and any other invalid non-finite case if appropriate), so
invalid coefficients cannot propagate into the native solver.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`:
- Around line 165-216: The deprecated public toDataModel() method is still being
used as the internal implementation for solve(SolverSettings) and
writeMPS(String), which makes the deprecation message misleading. Extract the
shared conversion logic into a private/package-private builder method such as
buildDataModel(), have solve and writeMPS call that internal helper, and keep
toDataModel() as a thin deprecated wrapper so external callers still see the
deprecation while internal paths avoid depending on the deprecated API.

In
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/QuadraticExpression.java`:
- Around line 108-110: The QuadraticExpression.dividedBy method has the same
missing division-by-zero guard as LinearExpression.dividedBy. Update
QuadraticExpression.dividedBy(double scalar) to validate the scalar before
calling times(1.0 / scalar), and make its behavior consistent with the
corresponding linear expression method by rejecting zero (and any other
unsupported values if applicable) with the same error handling pattern.

In
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/DataModelIntegrationTest.java`:
- Around line 354-389: The qp_diagonal_objective CaseSpec is setting
expectSolutionValues and expectedObjective even though
DataModelIntegrationTest.verify() exits early for cases with
hasQuadraticObjective() before assertSolution() runs, so these values are
misleading at the call site. Update this test setup to either remove those
unused expectations from the CaseSpec builder chain or move the checks into the
quadratic-path assertions in verify()/assertSolution() using the CaseSpec and
its hasQuadraticObjective()/withQuadraticObjective() flow.

In
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java`:
- Around line 20-26: The native test setup helpers are duplicated between
NativeIntegrationTest and DataModelIntegrationTest, so extract the shared logic
into a single test-support helper (for example, a NativeTestSupport class) and
have both tests call it. Move assumeNativeLibrary() and
assumeCudaDriverAvailable() into that shared class, keep the existing behavior
consistent across both callers, and align the ProcessBuilder setup so the two
copies do not drift (including the redirectErrorStream(true) detail where
needed).
- Around line 169-184: The small QP test is building the quadratic objective
from variables owned by a separate Problem, which relies on incidental matching
indices and is brittle. Update solvesSmallQP to make the intent explicit by
either constructing the expression from the DataModel/tinyLP variable context or
adding a clear comment near the Problem/addVariable and QuadraticExpression.of
usage that this is intentional index-only wiring, so future changes to tinyLP()
don’t silently break the test.
🪄 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: 3ca4aa57-ac64-49cf-9f79-73f9782af4c5

📥 Commits

Reviewing files that changed from the base of the PR and between 74e3d7e and c61bb62.

📒 Files selected for processing (56)
  • .github/workflows/build.yaml
  • .github/workflows/pr.yaml
  • .github/workflows/test.yaml
  • ci/build_java.sh
  • ci/release/update-version.sh
  • ci/test_java.sh
  • dependencies.yaml
  • docs/cuopt/source/cuopt-java/convex/convex-api.rst
  • docs/cuopt/source/cuopt-java/convex/convex-examples.rst
  • docs/cuopt/source/cuopt-java/convex/index.rst
  • docs/cuopt/source/cuopt-java/index.rst
  • docs/cuopt/source/cuopt-java/mip/index.rst
  • docs/cuopt/source/cuopt-java/mip/mip-api.rst
  • docs/cuopt/source/cuopt-java/mip/mip-examples.rst
  • docs/cuopt/source/cuopt-java/quick-start.rst
  • docs/cuopt/source/index.rst
  • java/.gitignore
  • java/cuopt/CMakeLists.txt
  • java/cuopt/README.md
  • java/cuopt/TESTS.md
  • java/cuopt/pom.xml
  • java/cuopt/scripts/build_native.sh
  • java/cuopt/scripts/generate_constants.sh
  • java/cuopt/scripts/test.sh
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/CSRMatrix.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Constraint.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ConstraintSense.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/CuOptException.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/LPStats.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/LinearExpression.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/MIPCallback.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/MIPCallbackSolution.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/MIPSetSolutionCallback.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/MIPSolutionCallback.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/MIPStats.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/NativeCuOpt.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ObjectiveExpression.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ObjectiveSense.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/PDLPSolverMode.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ProblemCategory.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/QuadraticConstraint.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/QuadraticExpression.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Solution.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/SolverMethod.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/SolverSettings.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/TerminationStatus.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Variable.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/VariableType.java
  • java/cuopt/src/main/native/cuopt_java_native_api.cpp
  • java/cuopt/src/main/native/cuopt_java_native_api.hpp
  • java/cuopt/src/main/native/cuopt_jni.cpp
  • java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/DataModelIntegrationTest.java
  • java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java
  • java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemModelingTest.java

Comment on lines +63 to +71
java-build-matrix:
needs: cpp-build
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/compute-matrix.yaml@main
with:
build_type: pull-request
matrix_name: conda-cpp-build
matrix_filter: map(select(.ARCH == "amd64"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow and the referenced shared workflow usage in context.
git ls-files .github/workflows/build.yaml .github/workflows/*.yaml .github/actions 2>/dev/null || true

printf '\n--- build.yaml (lines 1-220) ---\n'
nl -ba .github/workflows/build.yaml | sed -n '1,220p'

printf '\n--- search for build_type usage in workflows ---\n'
rg -n "build_type:\s*|inputs\.build_type|compute-matrix\.yaml|java-build-matrix|java-build" .github/workflows .github/actions -g '*.yml' -g '*.yaml' || true

Repository: NVIDIA/cuopt

Length of output: 713


🏁 Script executed:

#!/bin/bash
set -euo pipefail
git ls-files .github/workflows/build.yaml .github/workflows/*.yaml .github/actions 2>/dev/null || true

printf '\n--- build.yaml (lines 1-220) ---\n'
nl -ba .github/workflows/build.yaml | sed -n '1,220p'

printf '\n--- search for build_type usage in workflows ---\n'
rg -n "build_type:\s*|inputs\.build_type|compute-matrix\.yaml|java-build-matrix|java-build" .github/workflows .github/actions -g '*.yml' -g '*.yaml' || true

Repository: NVIDIA/cuopt

Length of output: 713


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- .github/workflows/build.yaml (lines 1-220) ---\n'
awk '{printf("%5d  %s\n", NR, $0)}' .github/workflows/build.yaml | sed -n '1,220p'

printf '\n--- search for workflow triggers / build_type in .github/workflows/build.yaml ---\n'
rg -n "on:|workflow_call|workflow_dispatch|schedule:|build_type:|java-build-matrix|java-build:" .github/workflows/build.yaml

Repository: NVIDIA/cuopt

Length of output: 10559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- usages of compute-matrix.yaml across repo ---\n'
rg -n "compute-matrix\.yaml@main|matrix_name: conda-cpp-build|matrix_filter:" .github/workflows -g '*.yml' -g '*.yaml'

printf '\n--- relevant workflow context around any other compute-matrix callsites ---\n'
for f in .github/workflows/*.yaml .github/workflows/*.yml; do
  [ -f "$f" ] || continue
  if rg -q "compute-matrix\.yaml@main" "$f"; then
    echo "### $f"
    awk '{printf("%5d  %s\n", NR, $0)}' "$f" | rg -n "compute-matrix\.yaml@main|matrix_name:|build_type:|needs:|strategy:|uses:"
    echo
  fi
done

Repository: NVIDIA/cuopt

Length of output: 9030


Use the workflow build_type for java-build-matrix.

.github/workflows/build.yaml:67-70 runs on push, tags, and manual nightly dispatch, but this matrix is still hardcoded to pull-request, so branch/nightly runs compute the wrong Java matrix.

Fix
-      build_type: pull-request
+      build_type: ${{ inputs.build_type || 'branch' }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
java-build-matrix:
needs: cpp-build
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/compute-matrix.yaml@main
with:
build_type: pull-request
matrix_name: conda-cpp-build
matrix_filter: map(select(.ARCH == "amd64"))
java-build-matrix:
needs: cpp-build
permissions:
contents: read
uses: rapidsai/shared-workflows/.github/workflows/compute-matrix.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
matrix_name: conda-cpp-build
matrix_filter: map(select(.ARCH == "amd64"))
🤖 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 @.github/workflows/build.yaml around lines 63 - 71, The java-build-matrix job
is still hardcoded to use pull-request, which causes the wrong Java matrix for
non-PR runs. Update the workflow input in build.yaml so the uses:
compute-matrix.yaml call for java-build-matrix passes the same build_type used
by the workflow instead of a fixed value, keeping the matrix selection aligned
with the trigger type.

Comment thread java/cuopt/CMakeLists.txt
Comment on lines +23 to +24
set(CUOPT_CUDA_RUNTIME_DIR "${CUOPT_PREFIX}/targets/x86_64-linux/lib"
CACHE PATH "CUDA runtime library directory")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Hardcoded x86_64-linux breaks aarch64 builds.

CUOPT_CUDA_RUNTIME_DIR and the include path both hardcode targets/x86_64-linux, but CONTRIBUTING.md explicitly lists aarch64 as a supported architecture. On an aarch64 conda environment this path won't exist, so CUOPT_CUDART_LIBRARY will fail the EXISTS check at line 36 with a misleading "not found" error rather than resolving correctly.

🛠️ Suggested fix: use `CMAKE_SYSTEM_PROCESSOR` or `find_package(CUDAToolkit)`
-set(CUOPT_CUDA_RUNTIME_DIR "${CUOPT_PREFIX}/targets/x86_64-linux/lib"
-    CACHE PATH "CUDA runtime library directory")
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
+  set(CUOPT_CUDA_TARGET_DIR "sbsa-linux")
+else()
+  set(CUOPT_CUDA_TARGET_DIR "x86_64-linux")
+endif()
+set(CUOPT_CUDA_RUNTIME_DIR "${CUOPT_PREFIX}/targets/${CUOPT_CUDA_TARGET_DIR}/lib"
+    CACHE PATH "CUDA runtime library directory")

As per coding guidelines, "Only Linux is supported" with architectures "x86_64 (64-bit)" and "aarch64 (64-bit)" per CONTRIBUTING.md.

Also applies to: 47-49

🤖 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 `@java/cuopt/CMakeLists.txt` around lines 23 - 24, The CUOPT CUDA runtime and
include paths are hardcoded to x86_64 and will break supported aarch64 builds.
Update the CUOPT_CUDA_RUNTIME_DIR and related include-path logic in
CMakeLists.txt to derive the target architecture dynamically, using
CMAKE_SYSTEM_PROCESSOR or CUDA toolkit discovery instead of embedding
x86_64-linux, so CUOPT_CUDART_LIBRARY resolves correctly on both x86_64 and
aarch64.

Source: Coding guidelines

Comment on lines +15 to +22
public CSRMatrix(double[] values, int[] columnIndices, int[] rowOffsets) {
this.rowOffsets = Arrays.copyOf(rowOffsets, rowOffsets.length);
this.columnIndices = Arrays.copyOf(columnIndices, columnIndices.length);
this.values = Arrays.copyOf(values, values.length);
if (this.values.length != this.columnIndices.length) {
throw new IllegalArgumentException("CSR values and column indices must have the same length");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Missing null-safety and rowOffsets consistency validation.

values/columnIndices/rowOffsets being null throws an NPE from Arrays.copyOf rather than a clear IllegalArgumentException. More importantly, there's no check that rowOffsets is internally consistent with the nnz count (e.g. rowOffsets[rowOffsets.length - 1] == values.length). Since this type marshals directly into the native JNI layer (NativeCuOpt.createProblem, setConstraintMatrix, etc.), a malformed rowOffsets array could propagate to native code and cause out-of-bounds reads there.

🛡️ Proposed validation
   public CSRMatrix(double[] values, int[] columnIndices, int[] rowOffsets) {
+    java.util.Objects.requireNonNull(values, "values");
+    java.util.Objects.requireNonNull(columnIndices, "columnIndices");
+    java.util.Objects.requireNonNull(rowOffsets, "rowOffsets");
     this.rowOffsets = Arrays.copyOf(rowOffsets, rowOffsets.length);
     this.columnIndices = Arrays.copyOf(columnIndices, columnIndices.length);
     this.values = Arrays.copyOf(values, values.length);
     if (this.values.length != this.columnIndices.length) {
       throw new IllegalArgumentException("CSR values and column indices must have the same length");
     }
+    if (this.rowOffsets.length == 0
+        || this.rowOffsets[this.rowOffsets.length - 1] != this.values.length) {
+      throw new IllegalArgumentException(
+          "CSR rowOffsets must be non-empty and end with the number of non-zero values");
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public CSRMatrix(double[] values, int[] columnIndices, int[] rowOffsets) {
this.rowOffsets = Arrays.copyOf(rowOffsets, rowOffsets.length);
this.columnIndices = Arrays.copyOf(columnIndices, columnIndices.length);
this.values = Arrays.copyOf(values, values.length);
if (this.values.length != this.columnIndices.length) {
throw new IllegalArgumentException("CSR values and column indices must have the same length");
}
}
public CSRMatrix(double[] values, int[] columnIndices, int[] rowOffsets) {
java.util.Objects.requireNonNull(values, "values");
java.util.Objects.requireNonNull(columnIndices, "columnIndices");
java.util.Objects.requireNonNull(rowOffsets, "rowOffsets");
this.rowOffsets = Arrays.copyOf(rowOffsets, rowOffsets.length);
this.columnIndices = Arrays.copyOf(columnIndices, columnIndices.length);
this.values = Arrays.copyOf(values, values.length);
if (this.values.length != this.columnIndices.length) {
throw new IllegalArgumentException("CSR values and column indices must have the same length");
}
if (this.rowOffsets.length == 0
|| this.rowOffsets[this.rowOffsets.length - 1] != this.values.length) {
throw new IllegalArgumentException(
"CSR rowOffsets must be non-empty and end with the number of non-zero values");
}
}
🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/CSRMatrix.java`
around lines 15 - 22, Add explicit validation in CSRMatrix’s constructor before
calling Arrays.copyOf: reject null values, columnIndices, and rowOffsets with a
clear IllegalArgumentException, then validate CSR consistency by checking that
the rowOffsets array is non-empty, starts at 0, is monotonic, and that its last
entry matches values.length (and therefore the nnz count). Keep the existing
values/columnIndices length check, and ensure the constructor fails fast with a
descriptive message so malformed matrices cannot reach NativeCuOpt.createProblem
or setConstraintMatrix.

Comment on lines +253 to +263
public DataModel setQuadraticObjectiveMatrix(double[] values, int[] indices, int[] offsets) {
double[] copiedValues = copy(values);
int[] copiedIndices = copy(indices);
int[] copiedOffsets = copy(offsets);
NativeCuOpt.setQuadraticObjectiveMatrix(handle(), copiedValues, copiedIndices, copiedOffsets);
quadraticObjectiveValues = copiedValues;
quadraticObjectiveIndices = copiedIndices;
quadraticObjectiveOffsets = copiedOffsets;
quadraticObjectiveMatrixSet = true;
return this;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Stale cached quadratic-objective data after mixing setter APIs.

setQuadraticObjective(QuadraticExpression) writes the new objective to native but never updates quadraticObjectiveValues/Indices/Offsets or resets quadraticObjectiveMatrixSet. If a caller previously invoked setQuadraticObjectiveMatrix(...) (which sets the flag and caches the CSR arrays) and later calls setQuadraticObjective(...), subsequent getQuadraticObjectiveValues/Indices/Offsets() calls will silently return the earlier cached data instead of the objective that was just set.

🛡️ Proposed fix — keep cache in sync
   public DataModel setQuadraticObjective(QuadraticExpression expression) {
     NativeCuOpt.setQuadraticObjective(
         handle(), quadraticRows(expression), quadraticColumns(expression), quadraticValues(expression));
+    quadraticObjectiveValues = quadraticValues(expression);
+    quadraticObjectiveIndices = quadraticColumns(expression);
+    // quadraticObjectiveOffsets requires row-offset construction consistent with expression's rows
+    quadraticObjectiveMatrixSet = true;
     return this;
   }

Also applies to: 335-351, 407-411

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java`
around lines 253 - 263, The quadratic objective cache in DataModel becomes stale
when mixing setQuadraticObjectiveMatrix(...) and
setQuadraticObjective(QuadraticExpression), because the latter updates native
state but leaves quadraticObjectiveValues/Indices/Offsets and
quadraticObjectiveMatrixSet unchanged. Update setQuadraticObjective(...) to keep
the cached CSR arrays and flag in sync with the new objective, and make sure the
getter methods getQuadraticObjectiveValues/Indices/Offsets() always reflect the
most recently set objective rather than old matrix data.

Comment on lines +413 to +500
public DataModel addQuadraticConstraint(Constraint constraint) {
if (!constraint.isQuadratic()) {
throw new IllegalArgumentException("Quadratic constraint requires quadratic terms");
}
if (constraint.getSense() == ConstraintSense.EQ) {
throw new IllegalArgumentException("Equality quadratic constraints are not supported");
}
QuadraticExpression expression = constraint.getQuadraticExpression();
LinearExpression linear = constraint.getLinearExpression();
int[] linearIndices = new int[linear.getTerms().size()];
double[] linearCoefficients = new double[linear.getTerms().size()];
int i = 0;
for (var entry : linear.getTerms().entrySet()) {
linearIndices[i] = entry.getKey().getIndex();
linearCoefficients[i] = entry.getValue();
i++;
}
NativeCuOpt.addQuadraticConstraint(
handle(),
quadraticRows(expression),
quadraticColumns(expression),
quadraticValues(expression),
linearIndices,
linearCoefficients,
constraint.getSense().nativeValue(),
constraint.getRHS());
quadraticConstraintNames.add(constraint.getConstraintName());
return this;
}

public DataModel addQuadraticConstraint(
String rowName,
double[] linearValues,
int[] linearIndices,
double rhs,
double[] values,
int[] rows,
int[] columns,
ConstraintSense sense) {
if (sense == ConstraintSense.EQ) {
throw new IllegalArgumentException("Equality quadratic constraints are not supported");
}
if (linearValues == null || linearIndices == null || linearValues.length != linearIndices.length) {
throw new IllegalArgumentException("linearValues and linearIndices must have the same length");
}
if (values == null || rows == null || columns == null
|| values.length != rows.length || values.length != columns.length) {
throw new IllegalArgumentException("quadratic COO arrays must have the same length");
}
NativeCuOpt.addQuadraticConstraint(
handle(),
copy(rows),
copy(columns),
copy(values),
copy(linearIndices),
copy(linearValues),
sense.nativeValue(),
rhs);
quadraticConstraintNames.add(rowName == null ? "" : rowName);
return this;
}

public List<QuadraticConstraint> getQuadraticConstraints() {
Object[] nativeConstraints = NativeCuOpt.getQuadraticConstraints(handle());
List<QuadraticConstraint> result = new ArrayList<>(nativeConstraints.length);
for (int i = 0; i < nativeConstraints.length; i++) {
Object[] entry = (Object[]) nativeConstraints[i];
int rowIndex = ((int[]) entry[0])[0];
String rowName = (String) entry[1];
if (i < quadraticConstraintNames.size() && !quadraticConstraintNames.get(i).isEmpty()) {
rowName = quadraticConstraintNames.get(i);
}
ConstraintSense sense = ConstraintSense.fromNative(((byte[]) entry[2])[0]);
double rhs = ((double[]) entry[5])[0];
result.add(
new QuadraticConstraint(
rowIndex,
rowName,
sense,
(double[]) entry[3],
(int[]) entry[4],
rhs,
(int[]) entry[6],
(int[]) entry[7],
(double[]) entry[8]));
}
return List.copyOf(result);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant Java sources and inspect the DataModel and parser entry points.
git ls-files 'java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/*.java' | sed -n '1,120p'

echo
echo "=== DataModel references ==="
rg -n "quadraticConstraintNames|addQuadraticConstraint|getQuadraticConstraints|read\\(|parseMPS\\(" \
  java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java \
  java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming -S

echo
echo "=== Parser / loader references ==="
rg -n "parseMPS|read\\(|quadratic constraint|QuadraticConstraint|QPS|MPS" \
  java/cuopt/src/main/java -S

echo
echo "=== Relevant DataModel excerpt ==="
sed -n '1,260p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

echo
echo "=== Later DataModel excerpt ==="
sed -n '260,560p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

Repository: NVIDIA/cuopt

Length of output: 33948


Quadratic constraint names can shift after parsing
read()/parseMPS() leave quadraticConstraintNames empty, so the first later addQuadraticConstraint(...) overrides the name of the first native quadratic constraint instead of the new one. Seed this list from the loaded model, or key names by native row index instead of insertion order.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java`
around lines 413 - 500, The quadratic constraint naming logic in DataModel is
using insertion order, so after read() or parseMPS() leaves
quadraticConstraintNames empty, the next addQuadraticConstraint(...) can
overwrite an existing native constraint’s name. Fix this by initializing
quadraticConstraintNames from the loaded model in the parsing/load path, or by
associating names with the native row index instead of list position; update the
addQuadraticConstraint overloads and getQuadraticConstraints so they resolve
names using a stable identifier rather than append order.

Comment on lines +278 to +310
for (int row = 0; row < dataModel.getNumConstraints(); row++) {
LinearExpression expression = new LinearExpression();
for (int p = rowOffsets[row]; p < rowOffsets[row + 1]; p++) {
expression = expression.plus(problem.getVariable(columnIndices[p]), values[p]);
}
ConstraintSense sense = ConstraintSense.fromNative(senses[row]);
if (constraintLowerBounds.length > row && constraintUpperBounds.length > row) {
if (Double.compare(constraintLowerBounds[row], constraintUpperBounds[row]) == 0) {
sense = ConstraintSense.EQ;
} else if (Double.compare(constraintLowerBounds[row], rhs[row]) == 0) {
sense = ConstraintSense.GE;
} else if (Double.compare(constraintUpperBounds[row], rhs[row]) == 0) {
sense = ConstraintSense.LE;
}
}
Constraint constraint;
switch (sense) {
case LE:
constraint = expression.le(rhs[row]);
break;
case GE:
constraint = expression.ge(rhs[row]);
break;
case EQ:
constraint = expression.eq(rhs[row]);
break;
default:
throw new IllegalStateException("Unsupported sense " + sense);
}
problem.addConstraint(
constraint,
rowNames.length > row && !rowNames[row].isEmpty() ? rowNames[row] : "c" + row);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "RANGE|ranged" java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java -i
rg -n "cuOptCreateRangedProblem|RANGES" cpp/include -i

Repository: NVIDIA/cuopt

Length of output: 1126


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Problem.java relevant ranges ==\n'
sed -n '1,220p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java | nl -ba | sed -n '1,220p'

printf '\n== DataModel.java ranged problem factory ==\n'
sed -n '1,180p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java | nl -ba | sed -n '1,180p'

printf '\n== Native API signatures ==\n'
sed -n '230,290p' cpp/include/cuopt/linear_programming/cuopt_c.h | nl -ba | sed -n '230,290p'

Repository: NVIDIA/cuopt

Length of output: 226


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Problem.java around the cited lines ==\n'
sed -n '240,330p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java | nl -ba | sed -n '240,330p'

printf '\n== DataModel.java around createRangedProblem ==\n'
sed -n '1,130p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java | nl -ba | sed -n '1,130p'

printf '\n== Search for read/readMPS entry points ==\n'
rg -n "readMPS|read\(" java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming -g '*.java'

Repository: NVIDIA/cuopt

Length of output: 233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "enum ConstraintSense|fromNative\\(" java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming -g '*.java'
sed -n '1,220p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ConstraintSense.java | nl -ba

Repository: NVIDIA/cuopt

Length of output: 1935


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Problem.java around the cited lines ==\n'
sed -n '268,312p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java

printf '\n== ConstraintSense.java ==\n'
sed -n '1,120p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/ConstraintSense.java

printf '\n== DataModel.java createRangedProblem and read path ==\n'
sed -n '60,120p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java
sed -n '440,520p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

printf '\n== Problem.java read entry points ==\n'
rg -n "readMPS|read\\(" java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

Repository: NVIDIA/cuopt

Length of output: 9648


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Problem.read / readMPS implementation ==\n'
sed -n '235,390p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java

printf '\n== Constraint bound getters in DataModel ==\n'
rg -n "getConstraintLowerBounds|getConstraintUpperBounds|getConstraintSense|getConstraintRHS|constraintLowerBounds|constraintUpperBounds" java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

printf '\n== DataModel constraint metadata methods around getters ==\n'
sed -n '180,260p' java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/DataModel.java

Repository: NVIDIA/cuopt

Length of output: 10118


Handle ranged rows instead of flattening them. Rows whose lower and upper bounds differ are rebuilt as a single-sided LE/GE/EQ constraint from rhs[row], so read/readMPS lose the original interval and can’t round-trip RANGES-style input. Add a range-aware path or reject those rows explicitly.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`
around lines 278 - 310, The constraint reconstruction logic in
Problem.read/Problem.readMPS is flattening ranged rows into a single LE/GE/EQ
constraint based on rhs[row], which drops the original interval bounds. Update
the row handling around the ConstraintSense selection and switch so rows with
differing constraintLowerBounds[row] and constraintUpperBounds[row] are either
preserved via a range-aware representation or explicitly rejected with a clear
error instead of being converted. Keep the existing single-sided path for
non-ranged rows and make the behavior around rowNames and addConstraint
consistent with the chosen handling.

Comment on lines +393 to +414
public void updateConstraint(Constraint constraint, Map<Variable, Double> coefficients, Double rhs) {
if (!constraints.contains(constraint)) {
throw new IllegalArgumentException("Constraint does not belong to this problem");
}
if (constraint.isQuadratic()) {
throw new IllegalArgumentException("updateConstraint applies to linear constraints only");
}
LinearExpression expression = new LinearExpression();
for (Map.Entry<Variable, Double> entry : constraint.getLinearExpression().getTerms().entrySet()) {
expression = expression.plus(entry.getKey(), entry.getValue());
}
if (coefficients != null) {
for (Map.Entry<Variable, Double> entry : coefficients.entrySet()) {
expression = expression.plus(entry.getKey(), entry.getValue() - constraint.getCoefficient(entry.getKey()));
}
}
constraint.updateLinearExpression(expression);
if (rhs != null) {
constraint.updateRHS(rhs);
}
resetSolvedValues();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

updateConstraint doesn't validate that new coefficient variables belong to this Problem, unlike updateObjective.

updateObjective explicitly throws IllegalArgumentException when a coefficient's key isn't in variables (line 419-421), but updateConstraint's coefficients loop (lines 404-408) has no equivalent check. A Variable from a different Problem instance could be merged into this constraint's LinearExpression, later producing a wrong column index in buildLinearConstraintMatrix() (via entry.getKey().getIndex()) that silently corrupts the CSR matrix passed to the native solver.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`
around lines 393 - 414, `updateConstraint` is missing the same ownership
validation that `updateObjective` uses for coefficient variables. In
`Problem.updateConstraint`, add a check in the `coefficients` loop to নিশ্চিত
each `Variable` key belongs to this `Problem`’s `variables` set and throw
`IllegalArgumentException` if not, before merging it into the
`LinearExpression`; keep the rest of the update flow in `updateConstraint`
unchanged.

Comment on lines +416 to +456
public void updateObjective(Map<Variable, Double> coefficients, Double constant, ObjectiveSense sense) {
if (coefficients != null) {
for (Map.Entry<Variable, Double> entry : coefficients.entrySet()) {
if (!variables.contains(entry.getKey())) {
throw new IllegalArgumentException("Objective variable does not belong to this problem");
}
entry.getKey().setObjectiveCoefficient(entry.getValue());
}
if (objectiveSet) {
LinearExpression updated =
LinearExpression.ofConstant(constant == null ? linearObjective.getConstant() : constant);
for (Map.Entry<Variable, Double> entry : linearObjective.getTerms().entrySet()) {
updated = updated.plus(entry.getKey(), coefficients.getOrDefault(entry.getKey(), entry.getValue()));
}
for (Map.Entry<Variable, Double> entry : coefficients.entrySet()) {
if (!linearObjective.getTerms().containsKey(entry.getKey())) {
updated = updated.plus(entry.getKey(), entry.getValue());
}
}
linearObjective = updated;
if (quadraticObjective != null) {
QuadraticExpression updatedQuadratic =
new QuadraticExpression().constant(linearObjective.getConstant());
for (Map.Entry<Variable, Double> entry : linearObjective.getTerms().entrySet()) {
updatedQuadratic = updatedQuadratic.plus(entry.getKey(), entry.getValue());
}
for (QuadraticExpression.QuadraticTerm term : quadraticObjective.getQuadraticTerms()) {
updatedQuadratic =
updatedQuadratic.plus(term.getFirst(), term.getSecond(), term.getCoefficient());
}
quadraticObjective = updatedQuadratic;
}
} else if (constant != null) {
linearObjective = LinearExpression.ofConstant(constant);
for (Variable variable : variables) {
if (variable.getObjectiveCoefficient() != 0.0) {
linearObjective = linearObjective.plus(variable, variable.getObjectiveCoefficient());
}
}
objectiveSet = true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

updateObjective leaves getObjective() stale when only per-variable coefficients are updated before any objective is set.

When coefficients != null, objectiveSet is false, and constant == null, the loop updates each Variable's own objectiveCoefficient (line 422), but neither linearObjective nor objectiveSet are touched — there's no corresponding branch for this case (only the objectiveSet == true branch and the objectiveSet == false && constant != null branch handle it). Since objectiveCoefficients() (used for solving, line 644) falls back to reading variable.getObjectiveCoefficient() when !objectiveSet, the actual solve is unaffected. However, getObjective() (line 470) returns the stale/empty linearObjective field, so introspection via problem.getObjective() will not reflect the coefficients that were just set — a real observable inconsistency for API consumers.

🤖 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
`@java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java`
around lines 416 - 456, The updateObjective method in Problem is leaving the
linearObjective field and objectiveSet flag unchanged when coefficients are
provided before any objective has been initialized and constant is null, so
getObjective() stays stale. Add a branch in updateObjective that rebuilds
linearObjective from the current variable objective coefficients (or otherwise
synchronizes linearObjective) and marks objectiveSet true for this
coefficients-only path, while keeping the existing ObjectiveSense handling and
compatibility with objectiveCoefficients() and getObjective().

Comment on lines +261 to +289
void mip_get_solution_callback(const cuopt_float_t* solution,
const cuopt_float_t* objective_value,
const cuopt_float_t* solution_bound,
void* user_data)
{
auto* context = static_cast<java_callback_context_t*>(user_data);
if (context == nullptr || context->callback == nullptr) { return; }

bool detach = false;
JNIEnv* env = get_callback_env(detach);
if (env == nullptr) { return; }

jclass cls = env->GetObjectClass(context->callback);
jmethodID method =
env->GetMethodID(cls, "onSolution", "([DDDLjava/lang/Object;)V");
if (method != nullptr) {
std::vector<cuopt_float_t> values(solution, solution + context->num_variables);
jdoubleArray solution_array = to_double_array(env, values);
env->CallVoidMethod(context->callback,
method,
solution_array,
static_cast<jdouble>(*objective_value),
static_cast<jdouble>(*solution_bound),
context->user_data);
env->DeleteLocalRef(solution_array);
}

if (detach) { g_jvm->DetachCurrentThread(); }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Local JNI ref leak in mip_get_solution_callback hot path.

GetObjectClass(context->callback) at Line 273 creates a local ref that is never released. If the callback is invoked repeatedly from the same already-attached thread within a single solve() call (i.e., detach == false, so no automatic cleanup on return), these refs accumulate in one native frame and can eventually overflow the JVM's local-reference table, crashing the process on long MIP solves.

🔒 Proposed fix
   jclass cls = env->GetObjectClass(context->callback);
   jmethodID method =
     env->GetMethodID(cls, "onSolution", "([DDDLjava/lang/Object;)V");
   if (method != nullptr) {
     std::vector<cuopt_float_t> values(solution, solution + context->num_variables);
     jdoubleArray solution_array = to_double_array(env, values);
     env->CallVoidMethod(context->callback,
                         method,
                         solution_array,
                         static_cast<jdouble>(*objective_value),
                         static_cast<jdouble>(*solution_bound),
                         context->user_data);
     env->DeleteLocalRef(solution_array);
   }
+  env->DeleteLocalRef(cls);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void mip_get_solution_callback(const cuopt_float_t* solution,
const cuopt_float_t* objective_value,
const cuopt_float_t* solution_bound,
void* user_data)
{
auto* context = static_cast<java_callback_context_t*>(user_data);
if (context == nullptr || context->callback == nullptr) { return; }
bool detach = false;
JNIEnv* env = get_callback_env(detach);
if (env == nullptr) { return; }
jclass cls = env->GetObjectClass(context->callback);
jmethodID method =
env->GetMethodID(cls, "onSolution", "([DDDLjava/lang/Object;)V");
if (method != nullptr) {
std::vector<cuopt_float_t> values(solution, solution + context->num_variables);
jdoubleArray solution_array = to_double_array(env, values);
env->CallVoidMethod(context->callback,
method,
solution_array,
static_cast<jdouble>(*objective_value),
static_cast<jdouble>(*solution_bound),
context->user_data);
env->DeleteLocalRef(solution_array);
}
if (detach) { g_jvm->DetachCurrentThread(); }
}
void mip_get_solution_callback(const cuopt_float_t* solution,
const cuopt_float_t* objective_value,
const cuopt_float_t* solution_bound,
void* user_data)
{
auto* context = static_cast<java_callback_context_t*>(user_data);
if (context == nullptr || context->callback == nullptr) { return; }
bool detach = false;
JNIEnv* env = get_callback_env(detach);
if (env == nullptr) { return; }
jclass cls = env->GetObjectClass(context->callback);
jmethodID method =
env->GetMethodID(cls, "onSolution", "([DDDLjava/lang/Object;)V");
if (method != nullptr) {
std::vector<cuopt_float_t> values(solution, solution + context->num_variables);
jdoubleArray solution_array = to_double_array(env, values);
env->CallVoidMethod(context->callback,
method,
solution_array,
static_cast<jdouble>(*objective_value),
static_cast<jdouble>(*solution_bound),
context->user_data);
env->DeleteLocalRef(solution_array);
}
env->DeleteLocalRef(cls);
if (detach) { g_jvm->DetachCurrentThread(); }
}
🤖 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 `@java/cuopt/src/main/native/cuopt_jni.cpp` around lines 261 - 289, The JNI
callback in mip_get_solution_callback leaks a local reference because
GetObjectClass(context->callback) is never paired with a DeleteLocalRef. Store
the jclass returned by GetObjectClass, use it for GetMethodID, and delete that
local ref before returning; keep the rest of the callback flow in cuopt_jni.cpp
unchanged.

Comment on lines +291 to +327
void mip_set_solution_callback(cuopt_float_t* solution,
cuopt_float_t* objective_value,
const cuopt_float_t* solution_bound,
void* user_data)
{
auto* context = static_cast<java_callback_context_t*>(user_data);
if (context == nullptr || context->callback == nullptr) { return; }

bool detach = false;
JNIEnv* env = get_callback_env(detach);
if (env == nullptr) { return; }

jclass cls = env->GetObjectClass(context->callback);
jmethodID method = env->GetMethodID(
cls, "getSolution", "(DLjava/lang/Object;)Lcom/nvidia/cuopt/mathematicalprogramming/MIPCallbackSolution;");
if (method != nullptr) {
jobject callback_solution =
env->CallObjectMethod(context->callback, method, static_cast<jdouble>(*solution_bound), context->user_data);
if (callback_solution != nullptr) {
jclass result_cls = env->GetObjectClass(callback_solution);
jfieldID solution_field = env->GetFieldID(result_cls, "solution", "[D");
jfieldID objective_field = env->GetFieldID(result_cls, "objectiveValue", "D");
if (solution_field != nullptr && objective_field != nullptr) {
auto solution_array =
static_cast<jdoubleArray>(env->GetObjectField(callback_solution, solution_field));
const auto values = get_double_array(env, solution_array);
if (values.size() == static_cast<size_t>(context->num_variables)) {
std::memcpy(solution, values.data(), values.size() * sizeof(cuopt_float_t));
*objective_value =
static_cast<cuopt_float_t>(env->GetDoubleField(callback_solution, objective_field));
}
}
}
}

if (detach) { g_jvm->DetachCurrentThread(); }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Multiple local JNI ref leaks and silent failure on size mismatch in mip_set_solution_callback.

cls (Line 303), callback_solution (Line 307-308), result_cls (Line 310), and the jdoubleArray returned by GetObjectField (Line 314-315) are all local refs that are never deleted — same reference-table-overflow risk as the sibling callback above, but worse since four refs leak per call in what is a solver hot path (repeatedly invoked to poll for injected incumbents).

Separately, when values.size() != context->num_variables (Line 317), the function silently does nothing — no exception, no log — making a user-side sizing bug in the callback impossible to diagnose.

🔒 Proposed fix
   jclass cls = env->GetObjectClass(context->callback);
   jmethodID method = env->GetMethodID(
     cls, "getSolution", "(DLjava/lang/Object;)Lcom/nvidia/cuopt/mathematicalprogramming/MIPCallbackSolution;");
   if (method != nullptr) {
     jobject callback_solution =
       env->CallObjectMethod(context->callback, method, static_cast<jdouble>(*solution_bound), context->user_data);
     if (callback_solution != nullptr) {
       jclass result_cls = env->GetObjectClass(callback_solution);
       jfieldID solution_field = env->GetFieldID(result_cls, "solution", "[D");
       jfieldID objective_field = env->GetFieldID(result_cls, "objectiveValue", "D");
       if (solution_field != nullptr && objective_field != nullptr) {
         auto solution_array =
           static_cast<jdoubleArray>(env->GetObjectField(callback_solution, solution_field));
         const auto values = get_double_array(env, solution_array);
         if (values.size() == static_cast<size_t>(context->num_variables)) {
           std::memcpy(solution, values.data(), values.size() * sizeof(cuopt_float_t));
           *objective_value =
             static_cast<cuopt_float_t>(env->GetDoubleField(callback_solution, objective_field));
         }
+        env->DeleteLocalRef(solution_array);
       }
+      env->DeleteLocalRef(result_cls);
+      env->DeleteLocalRef(callback_solution);
     }
   }
+  env->DeleteLocalRef(cls);
🤖 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 `@java/cuopt/src/main/native/cuopt_jni.cpp` around lines 291 - 327,
mip_set_solution_callback is leaking several JNI local references and silently
ignoring a solution-size mismatch. In this callback, delete the local refs
created by GetObjectClass, CallObjectMethod, GetObjectClass on
callback_solution, and GetObjectField for the solution array before returning,
ideally using a consistent cleanup path in mip_set_solution_callback. Also add
explicit handling for the values.size() != context->num_variables case in the
same function: surface an error via the existing JNI/error-reporting mechanism
or logging instead of doing nothing, so callback sizing bugs are diagnosable.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java (1)

87-105: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

problem is never closed, and the objective relies on an implicit default sense.

Every other test in this file wraps Problem in try-with-resources; here problem is created via plain assignment and never closed, leaking whatever native/Java-side state Problem.close() releases. Separately, no setObjective call is made — the test relies on addVariable's side-channel objective coefficient plus whatever ObjectiveSense Problem defaults to, which is fragile if that default ever changes.

🔧 Proposed fix
-    Problem problem = new Problem("integer");
-    Variable x = problem.addVariable(0, 10, 1.0, VariableType.INTEGER, "x");
-    problem.addConstraint(LinearExpression.of(x).ge(1.0));
-
-    try (SolverSettings settings = new SolverSettings().setSetting(CuOptConstants.CUOPT_TIME_LIMIT, 10.0);
-        Solution solution = problem.solve(settings)) {
-      assertTrue(solution.isMIP());
-      assertEquals(TerminationStatus.OPTIMAL, solution.getTerminationStatus());
-      assertEquals(1.0, x.getValue(), 1e-6);
-      assertDoesNotThrow(solution::getMIPStats);
-      assertThrows(IllegalStateException.class, solution::getDualSolution);
-      solution.close();
-      solution.close();
-    }
+    try (Problem problem = new Problem("integer")) {
+      Variable x = problem.addVariable(0, 10, 1.0, VariableType.INTEGER, "x");
+      problem.addConstraint(LinearExpression.of(x).ge(1.0));
+      problem.setObjective(x, ObjectiveSense.MINIMIZE);
+
+      try (SolverSettings settings = new SolverSettings().setSetting(CuOptConstants.CUOPT_TIME_LIMIT, 10.0);
+          Solution solution = problem.solve(settings)) {
+        assertTrue(solution.isMIP());
+        assertEquals(TerminationStatus.OPTIMAL, solution.getTerminationStatus());
+        assertEquals(1.0, x.getValue(), 1e-6);
+        assertDoesNotThrow(solution::getMIPStats);
+        assertThrows(IllegalStateException.class, solution::getDualSolution);
+        solution.close();
+        solution.close();
+      }
+    }
🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java`
around lines 87 - 105, Wrap the Problem instance in try-with-resources in
solvesProblemApiMIPAndLifecycleCloseIsIdempotent so it is closed like the other
tests, and make the objective explicit instead of relying on the default sense.
Use the existing Problem, SolverSettings, and solve flow to set the objective
with the intended ObjectiveSense before calling solve, then keep the current MIP
and close-idempotence assertions unchanged.
🧹 Nitpick comments (3)
java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java (3)

31-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Early return for QP cases leaves two branches permanently dead.

verify() returns before calling createSettings/assertSolution whenever hasQuadraticObjective() is true. Since the only case exercising hasQuadraticConstraint() (qp_diagonal_objective) also sets a quadratic objective, this means:

  • the else if (testCase.hasQuadraticObjective()) branch in createSettings (Lines 147-148) is unreachable, and
  • the quadratic-constraint feasibility check in CaseSpec.assertFeasible (Lines 707-729) is unreachable.

As a result, quadratic-constraint satisfaction is only checked structurally (construction), never against actual solved primal values. Consider either removing the now-dead branches, or exercising them with a solvable QP+quadratic-constraint case (or a dedicated lower-iteration-limit-tolerant assertion) so this coverage isn't silently lost.

🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java`
around lines 31 - 47, The early return in verify() for hasQuadraticObjective()
makes the quadratic-objective branch in createSettings() and the
quadratic-constraint feasibility path in CaseSpec.assertFeasible() unreachable.
Update verify(), createSettings(), and/or CaseSpec so quadratic-constraint
behavior is actually exercised by a solvable QP case, or remove the dead
branches if they are no longer needed; use the existing symbols
hasQuadraticObjective(), hasQuadraticConstraint(), createSettings(), and
assertFeasible() to locate the affected logic.

162-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate assumeNativeLibrary/assumeCudaDriverAvailable across test classes.

Both helpers are copy-pasted verbatim from NativeIntegrationTest, and they've already drifted slightly: this copy adds redirectErrorStream(true) to the nvidia-smi process while NativeIntegrationTest's copy doesn't. Consider extracting a shared test-support base class/utility to avoid future divergence.

🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java`
around lines 162 - 180, The helper methods assumeNativeLibrary and
assumeCudaDriverAvailable are duplicated from NativeIntegrationTest and have
already started to diverge, so extract them into a shared test-support base
class or utility and update ProblemIntegrationTest to call the shared
implementation. Keep the native library check and the CUDA driver probe in one
place so both test classes use the same behavior, and ensure the nvidia-smi
ProcessBuilder setup is centralized in the shared method.

612-618: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Constraint-count formula assumes ranged rows always produce exactly 2 constraints.

linearProblemConstraintCount() returns numConstraints * 2 for any ranged case, but createProblem() (Lines 499-505) only adds a lower/upper row when the corresponding bound is !Double.isInfinite(...). No current case has an actually-infinite ranged bound, so this passes today, but the formula and the construction logic will silently disagree the moment such a case is added. Separately, qp_diagonal_objective's upper bound is 1.0e20 — a large but finite double, not Double.POSITIVE_INFINITY — so it's unclear whether that value was meant to represent "effectively unbounded" (in which case it should use Double.POSITIVE_INFINITY to actually hit the skip branch) or a deliberately large finite bound.

🔧 Suggested fix to make the count formula match the construction logic
-    private int linearProblemConstraintCount() {
-      return isRanged() ? numConstraints * 2 : numConstraints;
-    }
+    private int linearProblemConstraintCount() {
+      if (!isRanged()) {
+        return numConstraints;
+      }
+      int count = 0;
+      for (int row = 0; row < numConstraints; row++) {
+        if (!Double.isInfinite(constraintLowerBounds[row])) {
+          count++;
+        }
+        if (!Double.isInfinite(constraintUpperBounds[row])) {
+          count++;
+        }
+      }
+      return count;
+    }
🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java`
around lines 612 - 618, The constraint-count logic in
linearProblemConstraintCount() is assuming every ranged row always contributes
two constraints, but createProblem() only adds each side when the bound is not
infinite. Update the counting helper to mirror the actual construction in
createProblem() by counting lower and upper contributions based on the same
bound checks, and review the qp_diagonal_objective upper bound to make sure it
intentionally uses a large finite value or is changed to a true unbounded value
if that was the intent.
🤖 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
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java`:
- Around line 87-105: Wrap the Problem instance in try-with-resources in
solvesProblemApiMIPAndLifecycleCloseIsIdempotent so it is closed like the other
tests, and make the objective explicit instead of relying on the default sense.
Use the existing Problem, SolverSettings, and solve flow to set the objective
with the intended ObjectiveSense before calling solve, then keep the current MIP
and close-idempotence assertions unchanged.

---

Nitpick comments:
In
`@java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java`:
- Around line 31-47: The early return in verify() for hasQuadraticObjective()
makes the quadratic-objective branch in createSettings() and the
quadratic-constraint feasibility path in CaseSpec.assertFeasible() unreachable.
Update verify(), createSettings(), and/or CaseSpec so quadratic-constraint
behavior is actually exercised by a solvable QP case, or remove the dead
branches if they are no longer needed; use the existing symbols
hasQuadraticObjective(), hasQuadraticConstraint(), createSettings(), and
assertFeasible() to locate the affected logic.
- Around line 162-180: The helper methods assumeNativeLibrary and
assumeCudaDriverAvailable are duplicated from NativeIntegrationTest and have
already started to diverge, so extract them into a shared test-support base
class or utility and update ProblemIntegrationTest to call the shared
implementation. Keep the native library check and the CUDA driver probe in one
place so both test classes use the same behavior, and ensure the nvidia-smi
ProcessBuilder setup is centralized in the shared method.
- Around line 612-618: The constraint-count logic in
linearProblemConstraintCount() is assuming every ranged row always contributes
two constraints, but createProblem() only adds each side when the bound is not
infinite. Update the counting helper to mirror the actual construction in
createProblem() by counting lower and upper contributions based on the same
bound checks, and review the qp_diagonal_objective upper bound to make sure it
intentionally uses a large finite value or is changed to a true unbounded value
if that was the intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 594299eb-4f80-4963-96fe-fd88c2b6acd4

📥 Commits

Reviewing files that changed from the base of the PR and between c61bb62 and 85e5440.

📒 Files selected for processing (12)
  • .github/workflows/pr.yaml
  • docs/cuopt/source/cuopt-java/convex/convex-api.rst
  • docs/cuopt/source/cuopt-java/convex/convex-examples.rst
  • docs/cuopt/source/cuopt-java/convex/index.rst
  • docs/cuopt/source/cuopt-java/quick-start.rst
  • java/cuopt/TESTS.md
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/NativeCuOpt.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/NativeProblem.java
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/Problem.java
  • java/cuopt/src/main/native/cuopt_jni.cpp
  • java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/NativeIntegrationTest.java
  • java/cuopt/src/test/java/com/nvidia/cuopt/mathematicalprogramming/ProblemIntegrationTest.java
💤 Files with no reviewable changes (3)
  • docs/cuopt/source/cuopt-java/convex/convex-examples.rst
  • java/cuopt/src/main/java/com/nvidia/cuopt/mathematicalprogramming/NativeCuOpt.java
  • java/cuopt/src/main/native/cuopt_jni.cpp
✅ Files skipped from review due to trivial changes (2)
  • java/cuopt/TESTS.md
  • docs/cuopt/source/cuopt-java/quick-start.rst
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/cuopt/source/cuopt-java/convex/index.rst
  • .github/workflows/pr.yaml

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants