Skip to content

[BUG] LinearExpression missing __neg__ / __pos__ / __abs__ (but Variable supports unary minus) #2056

Closed

Description

@ramakrishnap-nv

Describe the bug

LinearExpression does not implement __neg__, __pos__, or __abs__, while Variable does support unary minus. Negation therefore works on a single variable but fails as soon as the operand is an expression:

-x        # OK   -> LinearExpression
-(2*x)    # TypeError: bad operand type for unary -: 'LinearExpression'

This asymmetry is the problem. -x working teaches the user that unary minus is supported, and the identical idiom then raises as soon as the expression has more than one term.

It also makes the documented QP maximization workaround inexpressible. cuOpt QP is minimize-only and the guidance is "negate and minimize", but the natural way to write that raises:

problem.setObjective(-(2*a), sense=MINIMIZE)
# TypeError: bad operand type for unary -: 'LinearExpression'

Steps/Code to reproduce bug

from cuopt.linear_programming.problem import Problem, CONTINUOUS, LinearExpression

p = Problem("t")
x = p.addVariable(lb=0, ub=5, vtype=CONTINUOUS, name="x")
y = p.addVariable(lb=0, ub=5, vtype=CONTINUOUS, name="y")
e  = 2*x + 3*y
le = LinearExpression([x, y], [2.0, 3.0], 0.0)

-x       # OK    -> LinearExpression
-e       # FAIL  TypeError: bad operand type for unary -: 'LinearExpression'
-le      # FAIL  TypeError: bad operand type for unary -: 'LinearExpression'
+e       # FAIL  TypeError: bad operand type for unary +: 'LinearExpression'
abs(e)   # FAIL  TypeError: bad operand type for abs(): 'LinearExpression'

0 - e    # OK  (workaround)
e * -1   # OK  (workaround)
-1 * e   # OK  (workaround)
e - (x+y)  # OK  (binary __sub__ is implemented)

Expected behavior

-expr returns a LinearExpression with negated coefficients and constant, consistent with -variable and with __sub__. PuLP, Gurobi (gurobipy), and Pyomo all support unary negation on expressions, so this is the idiom users arrive with. __pos__ and __abs__ are lower priority, but __neg__ and __pos__ are one-liners given __mul__ already exists.

Environment details

  • Environment location: Bare-metal
  • Method of cuOpt install: conda — cuopt 26.08.00 cuda13_py312_260806_400863c1 from the nvidia channel, Python 3.12.14, CUDA 13.3, driver 590.48.01, Quadro RTX 8000

Additional context

Found while benchmarking LLM-generated cuOpt code (NL optimization problem → cuOpt model → execute → compare objective to gold) over 437 problems from the optimind benchmark set.

45 of 142 runtime errors (32%) from one model were this single missing operator — the largest single failure cause by a wide margin, ahead of every genuine modeling mistake. LLMs write -expr because it is idiomatic in every other Python modeling library, and because -x works in cuOpt.

This has an outsized effect on code generation specifically: a human hits the TypeError once, writes 0 - e, and moves on; a model generating single-shot code has no feedback loop and simply fails. Implementing __neg__ would remove roughly a third of the API-related failures we measured.

Activity

  1. chris-maes commented on Oct 6, 2026

    @chris-maes
    Contributor

    We support maximization for QPs. We should remove any docs that suggest we dont.

    We should support neg. We don't want to support abs (as this is not a linear expression---without additional variables and constraints). pos can be supported but seems lower priority.

  2. added a commit that references this issue on Oct 8, 2026
    0ed6886
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions