Skip to content

Conversation

@Iroy30
Copy link
Member

@Iroy30 Iroy30 commented Jan 14, 2026

Description

  • TODO: add doc and test
  • TODO: Update API names/deprecate old names

Issue

Checklist

  • I am familiar with the Contributing Guidelines.
  • Testing
    • New or existing tests cover these changes
    • Added tests
    • Created an issue to follow-up
    • NA
  • Documentation
    • The documentation is up to date with these changes
    • Added new documentation
    • NA

Summary by CodeRabbit

  • New Features

    • Support for matrix-based quadratic expressions for richer quadratic objectives.
    • New property to access computed objective values from solutions.
    • Obj accessor now consistently exposes quadratic or linear objectives as appropriate.
    • Improved sparse handling and caching for quadratic objective data.
  • Tests

    • Added/updated tests for matrix-form quadratic handling and CSR comparisons.
  • Chores

    • Added scipy to runtime dependencies.

✏️ Tip: You can customize this high-level summary in your review settings.

@Iroy30 Iroy30 requested review from a team as code owners January 14, 2026 01:26
@copy-pr-bot
Copy link

copy-pr-bot bot commented Jan 14, 2026

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

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Adds MQuadraticExpression (matrix-form quadratic), updates Problem to accept/store quadratic objectives as sparse COO/CSR, adds ObjValue property and adjusts Obj behavior/state handling, updates tests to export MQuadraticExpression and compare CSR components, and adds scipy to conda/runtime dependencies.

Changes

Cohort / File(s) Summary
Conda / runtime deps
conda/recipes/cuopt/recipe.yaml, conda/environments/all_cuda-129_arch-aarch64.yaml, conda/environments/all_cuda-129_arch-x86_64.yaml, conda/environments/all_cuda-131_arch-aarch64.yaml, conda/environments/all_cuda-131_arch-x86_64.yaml, dependencies.yaml
Added scipy (e.g., scipy>=1.13.0) to runtime/environment dependency lists.
Core implementation
python/cuopt/cuopt/linear_programming/problem.py
Added public class MQuadraticExpression (matrix-form quadratics); relaxed QuadraticExpression.__init__ defaults (vars=[], coefficients=[], constant=0.0); added Problem.ObjValue property; extended Problem.setObjective to accept MQuadraticExpression and build/store quadratic objective as sparse COO (cached/converted to CSR via getQcsr); changed internal quadratic objective default to None; updated Problem.Obj, reset/solution paths, and state handling to use COO/CSR and None semantics.
Tests & exports
python/cuopt/cuopt/tests/linear_programming/test_python_API.py
Exported/registered MQuadraticExpression in the module; added/updated tests for quadratic matrix handling and adjusted CSR comparisons (converted CSR arrays to lists for equality checks).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add QP matrix to problem modeling' directly and clearly describes the main change: introducing QP (quadratic programming) matrix support to the problem modeling system, which aligns with the primary objective of adding MQuadraticExpression and quadratic objective support throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Iroy30 Iroy30 added non-breaking Introduces a non-breaking change improvement Improves an existing functionality labels Jan 14, 2026
@Iroy30
Copy link
Member Author

Iroy30 commented Jan 14, 2026

/ok to test 2e8717a

@anandhkb anandhkb added this to the 26.02 milestone Jan 16, 2026
@Iroy30 Iroy30 requested a review from a team as a code owner January 19, 2026 03:45
@Iroy30
Copy link
Member Author

Iroy30 commented Jan 19, 2026

/ok to test 95560f5

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
python/cuopt/cuopt/linear_programming/problem.py (3)

1852-1867: Obj now returns MQuadraticExpression — potential compatibility break.

MQuadraticExpression lacks QuadraticExpression accessors (e.g., getVariables, getCoefficients, getLinearExpression). Existing clients calling problem.Obj or getObjective() for quadratic objectives may fail. Consider returning QuadraticExpression when appropriate, or add a compatibility shim/deprecation path. As per coding guidelines, ...


329-337: Use None instead of mutable default arguments in QuadraticExpression.__init__.

Mutable defaults (vars=[], coefficients=[]) are a Python anti-pattern. Although the current codebase explicitly passes arguments, these defaults could cause unintended state sharing if future code relies on them. The vars and coefficients lists are mutated via .extend() methods, making this a genuine concern.

Suggested fix
     def __init__(
-        self, qvars1, qvars2, qcoefficients, vars=[], coefficients=[], constant=0.0
+        self, qvars1, qvars2, qcoefficients, vars=None, coefficients=None, constant=0.0
     ):
+        if vars is None:
+            vars = []
+        if coefficients is None:
+            coefficients = []
         self.qvars1 = qvars1
         self.qvars2 = qvars2
         self.qcoefficients = qcoefficients
         self.vars = vars
         self.coefficients = coefficients
         self.constant = constant

1648-1700: Clear cached quadratic matrices when objective changes to prevent stale data.

When an objective is changed from quadratic to linear (or another type), the cached objective_qcsr_matrix remains and is returned by getQuadraticObjective(), causing incorrect solver behavior. The matrices must be cleared at the start of setObjective() before any case matching. Additionally, fix E128 indentation on the coo_matrix call and add explicit shape to MQuadraticExpression for consistency with QuadraticExpression.

🛠️ Suggested fix
     def setObjective(self, expr, sense=MINIMIZE):
         ...
         if self.solved:
             self.reset_solved_values()  # Reset all solved values
         self.ObjSense = sense
+        # Clear cached quadratic data whenever the objective is reset
+        self.objective_qcoo_matrix = None
+        self.objective_qcsr_matrix = None
         match expr:
             ...
             case QuadraticExpression():
                 ...
                 self.ObjConstant = expr.constant
                 qrows = [var.getIndex() for var in expr.qvars1]
                 qcols = [var.getIndex() for var in expr.qvars2]
                 self.objective_qcoo_matrix = coo_matrix(
                     (np.array(expr.qcoefficients),
-                    (np.array(qrows), np.array(qcols))),
-                    shape=(self.NumVariables, self.NumVariables)
+                     (np.array(qrows), np.array(qcols))),
+                    shape=(self.NumVariables, self.NumVariables),
                 )
             case MQuadraticExpression():
                 ...
                 self.ObjConstant = expr.constant
-                self.objective_qcoo_matrix = coo_matrix(
-                    expr.qmatrix)
+                self.objective_qcoo_matrix = coo_matrix(
+                    expr.qmatrix,
+                    shape=(self.NumVariables, self.NumVariables),
+                )
🤖 Fix all issues with AI agents
In `@conda/recipes/cuopt/recipe.yaml`:
- Line 85: Update the conda recipe dependency for scipy to include a minimum
version constraint (e.g., change the plain "scipy" entry in recipe.yaml to
"scipy >=1.10") to ensure compatibility with code using scipy.sparse.coo_matrix
and scipy.spatial.distance; make the version range consistent with other
scientific deps (for example "scipy >=1.10,<2.0" if you want an upper bound) and
update any CI/test environment metadata to match.

In `@python/cuopt/cuopt/linear_programming/problem.py`:
- Around line 861-872: The inline TODO and commented-out method lines violate
flake8 E265 (missing space after '#'); update the comment lines in this block so
every comment token has a single space after the '#' (e.g., change "## TODO: Add
matrix multiplication" to "# TODO: Add matrix multiplication" and "#def
__matmul__..." to "# def __matmul__(self, qcols):", likewise "#def
__rmatmul__..." to "# def __rmatmul__(self, qcols):") while keeping the same
text and references to the methods __matmul__, __rmatmul__ and attributes
qcols/qrows.
- Around line 746-859: The match statements in __iadd__, __add__, __isub__, and
__sub__ on MQuadraticExpression lack default branches, so unsupported operand
types cause implicit None returns; add a final "case _:" branch in each method
that handles unsupported types by raising a TypeError (or returning
NotImplemented in the non-inplace methods if you prefer Pythonic binary-operator
behavior) with a clear message including the method name and the operand's
actual type (e.g., refer to __iadd__/__isub__/__add__/__sub__,
MQuadraticExpression, Variable, LinearExpression in the message) so the operator
protocol is not violated and errors are explicit.
- Around line 716-741: The constructor of MQuadraticExpression uses mutable
defaults and the qmatrix may be a list-of-lists, causing shared-state bugs and
TypeError in getValue; update __init__ signature to use None for qvars, vars,
and coefficients and inside __init__ set them to [] if None, and also normalize
qmatrix with np.asarray(qmatrix, dtype=float) (store back to self.qmatrix) so
getValue can safely use self.qmatrix[i, j]; ensure constant and other attributes
remain set as before and no other code paths rely on the old default objects.
🧹 Nitpick comments (1)
python/cuopt/cuopt/tests/linear_programming/test_python_API.py (1)

819-848: Consider adding an edge‑case quadratic‑matrix scenario.

This test covers a feasible case; please add at least one degenerate/singleton or infeasible/unbounded case (or a variable‑ordering check) to exercise quadratic‑matrix handling under edge conditions. As per coding guidelines, ...

- pylibraft =${{ minor_version }}
- python
- rmm =${{ minor_version }}
- scipy
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for scipy imports in the Python cuopt package
rg -n "import scipy|from scipy" --type py

Repository: NVIDIA/cuopt

Length of output: 257


Addition of scipy dependency is justified.

The scipy dependency is actively used in the Python codebase for sparse matrix operations (scipy.sparse.coo_matrix in python/cuopt/cuopt/linear_programming/problem.py) and spatial distance calculations (scipy.spatial.distance in benchmark utilities). This confirms the addition is appropriate for QP matrix support.

Consider adding a minimum version constraint (e.g., scipy >=1.10) for consistency with other scientific Python dependencies (numpy >=1.23,<3.0, pandas >=2.0) and to ensure compatibility across the dependency stack.

🤖 Prompt for AI Agents
In `@conda/recipes/cuopt/recipe.yaml` at line 85, Update the conda recipe
dependency for scipy to include a minimum version constraint (e.g., change the
plain "scipy" entry in recipe.yaml to "scipy >=1.10") to ensure compatibility
with code using scipy.sparse.coo_matrix and scipy.spatial.distance; make the
version range consistent with other scientific deps (for example "scipy
>=1.10,<2.0" if you want an upper bound) and update any CI/test environment
metadata to match.

Comment on lines +716 to +741
def __init__(
self, qmatrix, qvars=[], vars=[], coefficients=[], constant=0.0
):
self.qmatrix = qmatrix
self.qvars = qvars
self.vars = vars
self.coefficients = coefficients
self.constant = constant


def shape(self):
return np.shape(self.qmatrix)

def getValue(self):
"""
Returns the value of the expression computed with the
current solution.
"""
# TODO: improve efficiency
value = 0.0
for i, var in enumerate(self.vars):
value += var.Value * self.coefficients[i]
for i, var1 in enumerate(self.qvars):
for j, var2 in enumerate(self.qvars):
value += var1.Value * var2.Value * float(self.qmatrix[i,j])
return value + self.constant
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the file exists and check the content around lines 716-741
wc -l python/cuopt/cuopt/linear_programming/problem.py

Repository: NVIDIA/cuopt

Length of output: 109


🏁 Script executed:

# Read the specific section mentioned in the review
sed -n '710,750p' python/cuopt/cuopt/linear_programming/problem.py

Repository: NVIDIA/cuopt

Length of output: 1242


🏁 Script executed:

# Let's also check the imports to understand what's available
head -30 python/cuopt/cuopt/linear_programming/problem.py

Repository: NVIDIA/cuopt

Length of output: 800


🏁 Script executed:

# Let's search for the MQuadraticExpression class definition
rg "class MQuadraticExpression" -A 50 python/cuopt/cuopt/linear_programming/problem.py

Repository: NVIDIA/cuopt

Length of output: 1521


🏁 Script executed:

# Verify numpy indexing behavior for list-of-lists
python3 << 'EOF'
import numpy as np

# Test with list-of-lists (as documented)
list_qmatrix = [[1.0, 2.0], [0.0, 0.0]]

# Try indexing directly - this should fail
try:
    result = list_qmatrix[0, 1]
    print(f"Direct list indexing [0,1]: {result}")
except TypeError as e:
    print(f"Direct list indexing [0,1] FAILS: {e}")

# Try with np.asarray
np_qmatrix = np.asarray(list_qmatrix)
try:
    result = np_qmatrix[0, 1]
    print(f"np.asarray then [0,1]: {result}")
except Exception as e:
    print(f"np.asarray then [0,1] FAILS: {e}")

# Also test that it works with float conversion
result = float(np_qmatrix[0, 1])
print(f"float(np.asarray(...)[0,1]): {result}")
EOF

Repository: NVIDIA/cuopt

Length of output: 173


🏁 Script executed:

# Check for other uses of qmatrix in the class
rg "qmatrix" python/cuopt/cuopt/linear_programming/problem.py -B 2 -A 2

Repository: NVIDIA/cuopt

Length of output: 2161


🏁 Script executed:

# Check for tests of MQuadraticExpression
find . -name "*test*" -type f | grep -E "\.(py|cpp)$" | xargs grep -l "MQuadraticExpression" 2>/dev/null || echo "No tests found"

Repository: NVIDIA/cuopt

Length of output: 120


🏁 Script executed:

# Check the tests for MQuadraticExpression
sed -n '1,50p' python/cuopt/cuopt/tests/linear_programming/test_python_API.py | head -100

Repository: NVIDIA/cuopt

Length of output: 1298


🏁 Script executed:

# Search for MQuadraticExpression usage in tests
grep -n "MQuadraticExpression" python/cuopt/cuopt/tests/linear_programming/test_python_API.py -B 2 -A 10

Repository: NVIDIA/cuopt

Length of output: 897


🏁 Script executed:

# Also check if there are other places in the code where MQuadraticExpression is instantiated
rg "MQuadraticExpression(" -B 2 -A 2 --type py

Repository: NVIDIA/cuopt

Length of output: 148


🏁 Script executed:

# Let's search for MQuadraticExpression more carefully with simpler pattern
grep -n "MQuadraticExpression" python/cuopt/cuopt/tests/linear_programming/test_python_API.py

Repository: NVIDIA/cuopt

Length of output: 127


🏁 Script executed:

# Let's look at the test_model_quad_expression function in detail
sed -n '820,850p' python/cuopt/cuopt/tests/linear_programming/test_python_API.py

Repository: NVIDIA/cuopt

Length of output: 1122


🏁 Script executed:

# Let's see if getValue is ever called in tests
rg "\.getValue\(\)" python/cuopt/cuopt/tests/linear_programming/ -B 3 -A 3

Repository: NVIDIA/cuopt

Length of output: 4632


🏁 Script executed:

# Check if there are any other files in the library that use MQuadraticExpression
rg "MQuadraticExpression" --type py | grep -v test

Repository: NVIDIA/cuopt

Length of output: 1218


Fix mutable defaults and list-of-lists indexing in MQuadraticExpression.

The __init__ method uses mutable default arguments (qvars=[], vars=[], coefficients=[]), which cause state to leak between instances. Additionally, getValue() uses self.qmatrix[i,j] indexing, which fails for list-of-lists (the documented input type) with a TypeError—it only works for numpy arrays. Replace defaults with None and normalize qmatrix to a numpy array via np.asarray().

🛠️ Suggested fix
-    def __init__(
-        self, qmatrix, qvars=[], vars=[], coefficients=[], constant=0.0
-    ):
-        self.qmatrix = qmatrix
-        self.qvars = qvars
-        self.vars = vars
-        self.coefficients = coefficients
+    def __init__(
+        self, qmatrix, qvars=None, vars=None, coefficients=None, constant=0.0
+    ):
+        self.qmatrix = np.asarray(qmatrix)
+        self.qvars = list(qvars) if qvars is not None else []
+        self.vars = list(vars) if vars is not None else []
+        self.coefficients = list(coefficients) if coefficients is not None else []
+        if self.qmatrix.ndim != 2 or self.qmatrix.shape[0] != self.qmatrix.shape[1]:
+            raise ValueError("qmatrix must be square")
+        if self.qvars and self.qmatrix.shape[0] != len(self.qvars):
+            raise ValueError("qmatrix size must match qvars length")
         self.constant = constant
📝 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
def __init__(
self, qmatrix, qvars=[], vars=[], coefficients=[], constant=0.0
):
self.qmatrix = qmatrix
self.qvars = qvars
self.vars = vars
self.coefficients = coefficients
self.constant = constant
def shape(self):
return np.shape(self.qmatrix)
def getValue(self):
"""
Returns the value of the expression computed with the
current solution.
"""
# TODO: improve efficiency
value = 0.0
for i, var in enumerate(self.vars):
value += var.Value * self.coefficients[i]
for i, var1 in enumerate(self.qvars):
for j, var2 in enumerate(self.qvars):
value += var1.Value * var2.Value * float(self.qmatrix[i,j])
return value + self.constant
def __init__(
self, qmatrix, qvars=None, vars=None, coefficients=None, constant=0.0
):
self.qmatrix = np.asarray(qmatrix)
self.qvars = list(qvars) if qvars is not None else []
self.vars = list(vars) if vars is not None else []
self.coefficients = list(coefficients) if coefficients is not None else []
if self.qmatrix.ndim != 2 or self.qmatrix.shape[0] != self.qmatrix.shape[1]:
raise ValueError("qmatrix must be square")
if self.qvars and self.qmatrix.shape[0] != len(self.qvars):
raise ValueError("qmatrix size must match qvars length")
self.constant = constant
def shape(self):
return np.shape(self.qmatrix)
def getValue(self):
"""
Returns the value of the expression computed with the
current solution.
"""
# TODO: improve efficiency
value = 0.0
for i, var in enumerate(self.vars):
value += var.Value * self.coefficients[i]
for i, var1 in enumerate(self.qvars):
for j, var2 in enumerate(self.qvars):
value += var1.Value * var2.Value * float(self.qmatrix[i,j])
return value + self.constant
🤖 Prompt for AI Agents
In `@python/cuopt/cuopt/linear_programming/problem.py` around lines 716 - 741, The
constructor of MQuadraticExpression uses mutable defaults and the qmatrix may be
a list-of-lists, causing shared-state bugs and TypeError in getValue; update
__init__ signature to use None for qvars, vars, and coefficients and inside
__init__ set them to [] if None, and also normalize qmatrix with
np.asarray(qmatrix, dtype=float) (store back to self.qmatrix) so getValue can
safely use self.qmatrix[i, j]; ensure constant and other attributes remain set
as before and no other code paths rely on the old default objects.

Comment on lines +746 to +859
def __iadd__(self, other):
# Compute quad_expr += lin_expr
match other:
case int() | float():
# Update just the constant value
self.constant += float(other)
return self
case Variable():
# Append just a variable with coefficient 1.0
self.vars.append(other)
self.coefficients.append(1.0)
return self
case LinearExpression():
# Append all linear variables, coefficients and constants
self.vars.extend(other.vars)
self.coefficients.extend(other.coefficients)
self.constant += other.constant
return self

def __add__(self, other):
# Compute quad_expr1 = quar_expr2 + lin_expr
match other:
case int() | float():
# Update just the constant value
return MQuadraticExpression(
self.qmatrix,
self.qvars,
self.vars,
self.coefficients,
self.constant + float(other),
)
case Variable():
# Append just a variable with coefficient 1.0
vars = self.vars + [other]
coeffs = self.coefficients + [1.0]
return MQuadraticExpression(
self.qmatrix,
self.qvars,
vars,
coeffs,
self.constant,
)
case LinearExpression():
# Append all linear variables, coefficients and constants
vars = self.vars + other.vars
coeffs = self.coefficients + other.coefficients
constant = self.constant + other.constant
return MQuadraticExpression(
self.qmatrix,
self.qvars,
vars,
coeffs,
constant,
)

def __isub__(self, other):
# Compute quad_expr -= lin_expr
match other:
case int() | float():
# Update just the constant value
self.constant -= float(other)
return self
case Variable():
# Append just a variable with coefficient -1.0
self.vars.append(other)
self.coefficients.append(-1.0)
return self
case LinearExpression():
# Append all linear variables, coefficients and constants
self.vars.extend(other.vars)
for coeff in other.coefficients:
self.coefficients.append(-coeff)
self.constant -= other.constant
return self

def __sub__(self, other):
# Compute quad_expr2 = quad_expr1 - lin_expr
match other:
case int() | float():
# Update just the constant value
return MQuadraticExpression(
self.qmatrix,
self.qvars,
self.vars,
self.coefficients,
self.constant - float(other),
)
case Variable():
# Append just a variable with coefficient -1.0
vars = self.vars + [other]
coeffs = self.coefficients + [-1.0]
return MQuadraticExpression(
self.qmatrix,
self.qvars,
vars,
coeffs,
self.constant,
)
case LinearExpression():
# Append all linear variables, coefficients and constants
vars = self.vars + other.vars
coeffs = []
for i in self.coefficients:
coeffs.append(i)
for i in other.coefficients:
coeffs.append(-1.0 * i)
constant = self.constant - other.constant
return MQuadraticExpression(
self.qmatrix,
self.qvars,
vars,
coeffs,
constant,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "problem.py" | grep -E "linear_programming|cuopt"

Repository: NVIDIA/cuopt

Length of output: 106


🏁 Script executed:

cat -n ./python/cuopt/cuopt/linear_programming/problem.py | sed -n '746,859p'

Repository: NVIDIA/cuopt

Length of output: 5191


🏁 Script executed:

grep -n "def __" ./python/cuopt/cuopt/linear_programming/problem.py | head -20

Repository: NVIDIA/cuopt

Length of output: 709


Add explicit error handling for unsupported operand types in operator overloads.

The __iadd__, __add__, __isub__, and __sub__ methods lack default cases in their match statements. When unsupported types are passed, they implicitly return None, breaking Python's operator protocol. For __iadd__ and __isub__, this silently converts the object to None. For __add__ and __sub__, this returns None instead of an expression.

Add default case with error handling
     def __iadd__(self, other):
         # Compute quad_expr += lin_expr
         match other:
             case int() | float():
                 ...
                 return self
             case Variable():
                 ...
                 return self
             case LinearExpression():
                 ...
                 return self
+            case _:
+                raise TypeError(
+                    "unsupported operand type(s) for +=: 'MQuadraticExpression' and '%s'"
+                    % type(other).__name__
+                )

Apply the same fix to __add__, __isub__, and __sub__.

🤖 Prompt for AI Agents
In `@python/cuopt/cuopt/linear_programming/problem.py` around lines 746 - 859, The
match statements in __iadd__, __add__, __isub__, and __sub__ on
MQuadraticExpression lack default branches, so unsupported operand types cause
implicit None returns; add a final "case _:" branch in each method that handles
unsupported types by raising a TypeError (or returning NotImplemented in the
non-inplace methods if you prefer Pythonic binary-operator behavior) with a
clear message including the method name and the operand's actual type (e.g.,
refer to __iadd__/__isub__/__add__/__sub__, MQuadraticExpression, Variable,
LinearExpression in the message) so the operator protocol is not violated and
errors are explicit.

Comment on lines +861 to +872
## TODO: Add matrix multiplication
#def __matmul__(self, qcols):
# if not self.qcols:
# self.qcols = qcols
# else:
# raise Exception("")

#def __rmatmul__(self, qcols):
# if not self.qrows:
# self.qrows = qrows
# else:
# raise Exception("")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix comment spacing to satisfy flake8 E265.

🧹 Suggested fix
-    `#def` __matmul__(self, qcols):
+    # def __matmul__(self, qcols):
 ...
-    `#def` __rmatmul__(self, qcols):
+    # def __rmatmul__(self, qcols):
📝 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
## TODO: Add matrix multiplication
#def __matmul__(self, qcols):
# if not self.qcols:
# self.qcols = qcols
# else:
# raise Exception("")
#def __rmatmul__(self, qcols):
# if not self.qrows:
# self.qrows = qrows
# else:
# raise Exception("")
## TODO: Add matrix multiplication
# def __matmul__(self, qcols):
# if not self.qcols:
# self.qcols = qcols
# else:
# raise Exception("")
# def __rmatmul__(self, qcols):
# if not self.qrows:
# self.qrows = qrows
# else:
# raise Exception("")
🧰 Tools
🪛 Flake8 (7.3.0)

[error] 862-862: block comment should start with '# '

(E265)


[error] 868-868: block comment should start with '# '

(E265)

🤖 Prompt for AI Agents
In `@python/cuopt/cuopt/linear_programming/problem.py` around lines 861 - 872, The
inline TODO and commented-out method lines violate flake8 E265 (missing space
after '#'); update the comment lines in this block so every comment token has a
single space after the '#' (e.g., change "## TODO: Add matrix multiplication" to
"# TODO: Add matrix multiplication" and "#def __matmul__..." to "# def
__matmul__(self, qcols):", likewise "#def __rmatmul__..." to "# def
__rmatmul__(self, qcols):") while keeping the same text and references to the
methods __matmul__, __rmatmul__ and attributes qcols/qrows.

@Iroy30
Copy link
Member Author

Iroy30 commented Jan 19, 2026

/ok to test c2b2032

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
conda/environments/all_cuda-129_arch-x86_64.yaml (1)

1-2: Update the source dependencies.yaml file instead of editing the auto-generated file directly.

This file is auto-generated from ../../dependencies.yaml. The header comment instructs that changes must be made to the source file and then rapids-dependency-file-generator must be run to regenerate this file. The scipy dependency was added only to this auto-generated file, not to the source. On the next regeneration, this change will be lost.

Please add scipy to dependencies.yaml and regenerate this file to ensure the dependency persists.

🤖 Fix all issues with AI agents
In `@conda/environments/all_cuda-131_arch-aarch64.yaml`:
- Line 64: The generated environment includes "scipy" but the source
dependencies.yaml is missing it, so add a scipy entry to dependencies.yaml
(e.g., scipy>=1.15,<2.0 or another range compatible with your NumPy constraint
>=1.23.5,<3.0) and then regenerate the conda environment file so scipy is
preserved; ensure the dependency name matches "scipy" exactly and the version
pin is consistent with Python 3.10–3.13 and aarch64 conda-forge packages.

@Iroy30
Copy link
Member Author

Iroy30 commented Jan 19, 2026

/ok to test 254a5a9

raise Exception("Quadratic constraints not supported")


class MQuadraticExpression:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need a separate MQuadraticExpression class? Could we just add another way to initialize a regular QuadraticExpression class?

Copy link
Contributor

@chris-maes chris-maes left a comment

Choose a reason for hiding this comment

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

Could we discuss the need for MQuadraticExpression before merging?

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

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants