Skip to content

Add support for reduced cost#18

Merged
ZedongPeng merged 2 commits intomainfrom
support_reduced_cost
Jan 30, 2026
Merged

Add support for reduced cost#18
ZedongPeng merged 2 commits intomainfrom
support_reduced_cost

Conversation

@ZedongPeng
Copy link
Copy Markdown
Collaborator

Fixes #17

Summary

This PR adds support for reduced cost.

Current Issue

The current wrapper fails test_solve_VariableIndex_ConstraintDual_MAX_SENSE because cuPDLPx uses a boxed-bounds representation, so xl and xu can map to the same internal index.

Question

  • Should we branch on the set type (GreaterThan, LessThan, EqualTo, Interval) when returning ConstraintDual for VariableIndex constraints?
  • One more question. Does it also apply to ConstraintDual for MOI.ScalarAffineFunction?

@blegat @odow

Test code

import MathOptInterface as MOI
using CuPDLPx

T = Float64
import MathOptInterface.Utilities as MOIU


model = MOI.instantiate(CuPDLPx.Optimizer; with_cache_type = T)

x  = MOI.add_variable(model)
xl = MOI.add_constraint(model, x, MOI.GreaterThan(T(1)))
xu = MOI.add_constraint(model, x, MOI.LessThan(T(1)))

MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), x)
MOI.set(model, MOI.RawOptimizerAttribute("verbose"), true)

MOI.optimize!(model)

sl = MOI.get(model, MOI.ConstraintDual(), xl)
su = MOI.get(model, MOI.ConstraintDual(), xu)

@show sl su sl+su

Output

---------------------------------------------------------------------------------------
                                    cuPDLPx v0.2.5                                    
                        A GPU-Accelerated First-Order LP Solver                        
               (c) Haihao Lu, Massachusetts Institute of Technology, 2025              
---------------------------------------------------------------------------------------
problem: 0 rows, 1 columns, 0 nonzeros
settings:
  iter_limit         : 2147483647
  time_limit         : 3600.00 sec
  eps_opt            : 1.0e-04
  eps_feas           : 1.0e-04

Running presolver (PSLP v0.0.4)...
  status          : REDUCED
  presolve time   : 0.000205 sec
  reduced problem : 0 rows, 0 columns, 0 nonzeros
---------------------------------------------------------------------------------------
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000205 sec
  Precondition time      : 0 sec
  Solve time             : 0 sec
  Iterations             : 0
  Primal objective       : -1
  Dual objective         : -1
  Objective gap          : 0.000e+00
  Primal infeas          : 0.000e+00
  Dual infeas            : 0.000e+00

ci.value = 1
ci.value = 1
sl = -1.0
su = -1.0
sl + su = -2.0
-2.0

@odow
Copy link
Copy Markdown
Contributor

odow commented Jan 30, 2026

You need to return the correct dual for each set. The return value is not the reduced cost irrespective of the set.

It's hard to give generic advice, because it depends on your convention for the sign of the duals.

Our convention is:

  • for GreaterThan, the dual is non-negative.
  • for LessThan, the dual is non-positive
  • for Interval and EqualTo, the dual sign corresponds to which "side" is binding

There are tests for all these cases which should help you figure it out.

If you're passing tests for ScalarAffine, it probably means that you have the same dual convention as us.

Here's an example from Clp.jl:

https://github.com/jump-dev/Clp.jl/blob/9b7c630fae02917f952edc0e80057fae8445687b/src/MOI_wrapper/MOI_wrapper.jl#L672-L727

@ZedongPeng
Copy link
Copy Markdown
Collaborator Author

Thank you @odow .

Tests passed.

[ Info: Running test_solve_VariableIndex_ConstraintDual_MAX_SENSE
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000152 sec
  Precondition time      : 0 sec
  Solve time             : 0 sec
  Iterations             : 0
  Primal objective       : -1
  Dual objective         : -1
  Objective gap          : 0.000e+00
  Primal infeas          : 0.000e+00
  Dual infeas            : 0.000e+00
[ Info: Running test_solve_VariableIndex_ConstraintDual_MIN_SENSE
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000112 sec
  Precondition time      : 0 sec
  Solve time             : 0 sec
  Iterations             : 0
  Primal objective       : 1
  Dual objective         : 1
  Objective gap          : 0.000e+00
  Primal infeas          : 0.000e+00
  Dual infeas            : 0.000e+00
[ Info: Running test_variable_solve_with_lowerbound
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000122 sec
  Precondition time      : 0 sec
  Solve time             : 0 sec
  Iterations             : 0
  Primal objective       : 2
  Dual objective         : 2
  Objective gap          : 0.000e+00
  Primal infeas          : 0.000e+00
  Dual infeas            : 0.000e+00
[ Info: Running test_variable_solve_with_upperbound
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000106 sec
  Precondition time      : 0 sec
  Solve time             : 0 sec
  Iterations             : 0
  Primal objective       : -2
  Dual objective         : -2
  Objective gap          : 0.000e+00
  Primal infeas          : 0.000e+00
  Dual infeas            : 0.000e+00
Test Summary: | Pass  Total   Time
test_runtests |   30     30  11.8s
     Testing CuPDLPx tests passed 

Comment thread src/MOI_wrapper.jl
Comment thread src/MOI_wrapper.jl
Comment thread src/MOI_wrapper.jl
Comment thread src/MOI_wrapper.jl
@ZedongPeng ZedongPeng merged commit 67c675c into main Jan 30, 2026
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.

Querying dual of a FixRef is very slow on larger models

3 participants