Skip to content

Conversation

@odow
Copy link
Member

@odow odow commented Jul 2, 2025

Closes #302

@eminyouskn
Copy link
Collaborator

@odow Do you want me to fix the tests that failed? I know how to fix them because it was part of the PR that I did.

@odow
Copy link
Member Author

odow commented Jul 2, 2025

I can fix them. You don't have access to the license so CI won't run.

@odow
Copy link
Member Author

odow commented Jul 2, 2025

I should probably figure out how to test both KNITRO 14 and 15. It makes me a bit suspicious if there are breaking changes and we're testing only a single version.

@codecov
Copy link

codecov bot commented Jul 3, 2025

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.09%. Comparing base (ecaceef) to head (639c42f).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/KNITRO.jl 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #327      +/-   ##
==========================================
+ Coverage   94.07%   94.09%   +0.01%     
==========================================
  Files           3        3              
  Lines        1013     1016       +3     
==========================================
+ Hits          953      956       +3     
  Misses         60       60              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eminyouskn
Copy link
Collaborator

It is weird that the test_constraint_ZeroOne_bounds_3 is passing on windows and not on linux. I would suggest to remove the silencing of the solver MOI.set(model, MOI.Silent(), true) so we can see what is the reason of this. I suspect that for linux the problem will be the same problem I had which is that KNITRO does not allow adding a non integer bound on binary variables and throw an error. I am surprised that this does not throw an error on windows. For the SOC errors, I had the same issues and fixed them using the reformulation of the SOC constraint using quadratic constraint instead of KN_add_con_L2Norm. There is also a problem with test_linear_integer_solve_twice which is also weird because in this test we just solve two times the same problem and it should not change the status from LOCALLY_SOLVED to LOCALLY_INFEASIBLE. On my computer it gives that the first constraints of integrality is not satisfied during the second solve.

@odow
Copy link
Member Author

odow commented Jul 3, 2025

It is weird that the test_constraint_ZeroOne_bounds_3 is passing on windows and not on linux

This suggests that there is a much deeper issue in the code and that we're probably doing something wrong, somewhere. It doesn't even mean that the problem is related to the binary variables.

@eminyouskn
Copy link
Collaborator

Yes totally agree. I am investigating the problem using the C interface.

@eminyouskn
Copy link
Collaborator

It seems that the problem comes from the fact that "setting bounds on binary variables has undefined behaviour". I suggest that when we want to add x <= b or x >= a for a binary variable x, we add it as a constraint instead of setting it as a bound on x. This was the fix I implemented in my first PR.

@odow
Copy link
Member Author

odow commented Jul 4, 2025

setting bounds on binary variables has undefined behaviour

Do you have a link to the documentation where it says this?

@eminyouskn
Copy link
Collaborator

eminyouskn commented Jul 4, 2025

I reported the issue internally to the team, and one of the Knitro development team members said that it is illegal to set bounds on binary variables. I did not see any documentation about it.

@odow
Copy link
Member Author

odow commented Jul 4, 2025

I just realized that you work at Artelys 😄

If it is illegal to set bounds on variables, then why isn't the API erroring when we try?

The correct fix is to just make the variables general integer with 0-1 bounds and store the original bounds to disambiguate implied vs user-set bounds. We should not add a new linear constraint.

I've sent you a maintainer invite to this repo so that you can push to the jump-dev remote and have CI work, etc. Just don't merge anything to master without review.

I have some time next week to look at this properly. I've been a bit busy this week.

@eminyouskn
Copy link
Collaborator

If it is illegal to set bounds on variables, then why isn't the API erroring when we try?

I don't know. I joined Artelys only 3 months ago.

The correct fix is to just make the variables general integer with 0-1 bounds and store the original bounds to disambiguate implied vs user-set bounds. We should not add a new linear constraint.

I think we can do this as well. I am just curious about how to enforce the bounds on the solver. Would it be also adding linear constraints to do that ?

I've sent you a maintainer invite to this repo so that you can push to the jump-dev remote and have CI work, etc. Just don't merge anything to master without review.

Thank you for the invite. I just accept it.

I have some time next week to look at this properly. I've been a bit busy this week.

Thanks again. I will try also to do it next week as well.

@mgabay
Copy link

mgabay commented Jul 5, 2025

I agree the solver should raise an error in such a case. We'll fix that in a future version.
For the doc, we may add a word too it just did not go through our mind that someone would want to set bounds on a 0-1 variable. The only thing that make sense with bounds on binary variables is to use the setbound function for fixing the variable to 0 or 1.
@eminyouskn : I am not sure what you tried to achieve with this example. it is good that you had it to detect this issue. For the use case, please discuss it with Marvin next week.

@eminyouskn
Copy link
Collaborator

Thanks for your answer, @mgabay. I didn't write the example; it's part of the MathOptInterface test suite. I was planning to discuss it with Marvin next week as you said..

@odow
Copy link
Member Author

odow commented Jul 6, 2025

it just did not go through our mind that someone would want to set bounds on a 0-1 variable

@mgabay 😆 unfortunately many of the tests that we have added to MOI over the years are motivated by bugs reported on the JuMP forum. It's say it is actually not unusual for people to add (even fractional) bounds to binary variables! Yes, in theory they could work out that they should remove/fix a binary variable. But people write very weird models.

@joaquimg I wonder if this is another thing ModelAnalyzer could check?

@odow
Copy link
Member Author

odow commented Jul 8, 2025

The solve-twice thing is a bug in KNITRO:

julia> kn = KNITRO.KN_new()
##### This license is only intended for use by For JuMP dev CI/CD only. #####
##### License is valid until Jan 31, 2026 #####
15.0.0
-----------------------
Problem Characteristics
-----------------------
Objective goal:  Minimize
Objective type:  -1
Number of variables:                             0
Number of constraints:                           0
Number of nonzeros in Jacobian:                  0
Number of nonzeros in Hessian:                   0


julia> KNITRO.KN_load_mps_file(kn, "bug.mps")
0

julia> KNITRO.KN_solve(kn)

=======================================
            Student License
       (NOT FOR COMMERCIAL USE)
         Artelys Knitro 15.0.0
=======================================

No start point provided -- Knitro computing one.

Knitro presolve eliminated 0 variables and 0 constraints.

feastol                  1e-06
feastol_abs              1e-06
opttol                   1e-06
opttol_abs               0.001
The problem is identified as a MILP.
Knitro changing mip_method from AUTO to 1.
Knitro changing mip_root_lpalg from AUTO to 3.
Knitro changing mip_node_lpalg from AUTO to 2.
Knitro changing mip_branchrule from AUTO to 2.
Knitro changing mip_selectrule from AUTO to 2.
Knitro changing mip_mir from AUTO to 2.
Knitro changing mip_clique from AUTO to 0.
Knitro changing mip_zerohalf from AUTO to 0.
Knitro changing mip_liftproject from AUTO to 0.
Knitro changing mip_knapsack from AUTO to 2.
Knitro changing mip_gomory from AUTO to 1.
Knitro changing mip_cut_flowcover from AUTO to 2.
Knitro changing mip_cut_probing from AUTO to 1.
Knitro changing mip_rounding from AUTO to 3.
Knitro changing mip_heuristic_strategy from AUTO to 1.
Knitro changing mip_heuristic_feaspump from AUTO to 0.
Knitro changing mip_heuristic_misqp from AUTO to 0.
Knitro changing mip_heuristic_mpec from AUTO to 0.
Knitro changing mip_heuristic_diving from AUTO to 0.
Knitro changing mip_heuristic_lns from AUTO to 0.
Knitro changing mip_heuristic_localsearch from AUTO to 1.
Knitro changing mip_pseudoinit from AUTO to 1.

Problem Characteristics                                 (   Presolved)
-----------------------
Objective goal:  Maximize
Objective type:  linear
Number of variables:                                  2 (           2)
    bounded below only:                               0 (           0)
    bounded above only:                               0 (           0)
    bounded below and above:                          2 (           2)
    fixed:                                            0 (           0)
    free:                                             0 (           0)
Number of binary variables:                           1 (           1)
Number of integer variables:                          1 (           1)
Number of constraints:                                1 (           2)
    linear equalities:                                0 (           0)
    quadratic equalities:                             0 (           0)
    gen. nonlinear equalities:                        0 (           0)
    linear one-sided inequalities:                    0 (           2)
    quadratic one-sided inequalities:                 0 (           0)
    gen. nonlinear one-sided inequalities:            0 (           0)
    linear two-sided inequalities:                    1 (           0)
    quadratic two-sided inequalities:                 0 (           0)
    gen. nonlinear two-sided inequalities:            0 (           0)
Number of nonzeros in Jacobian:                       2 (           4)
Number of nonzeros in Hessian:                        0 (           0)

Knitro using Branch and Bound method with 8 threads.

Initial points
--------------
No initial point provided for the root node relaxation.
No primal point provided for the MIP.

Root node relaxation
--------------------

 Iter      Objective      Feasibility        Optimality       Time 
                             error              error        (secs)
 ----      ---------      -----------        ----------      ------
    1       0.125000      0.00000e+00          0.353753       0.000
    2       0.561072      0.00000e+00          0.266093       0.001
    3       0.943188      0.00000e+00       4.00827e-02       0.001
    4       0.999716      0.00000e+00       2.21843e-03       0.001
    5       0.999999      0.00000e+00       4.90567e-06       0.001
    6        1.00000      0.00000e+00       2.40654e-11       0.001
    7        1.00000      0.00000e+00       3.33067e-16       0.001
    8        1.00000      0.00000e+00       0.00000e+00       0.001

Root node cutting planes
------------------------

 Iter     Cuts      Best solution   Best bound      Gap       Time 
                        value         value                  (secs)
 ----     ----      -------------   ----------      ---      ------
    0        0       1.00000   LS      1.00000      0.00%     0.001

EXIT: Optimal solution found.

Final Statistics for MIP
------------------------
Final objective value               =  1.00000000000000e+00
Final bound value                   =  1.00000000000000e+00
Final optimality gap (abs / rel)    =  0.00e+00 / 0.00e+00 (0.00%)
# of root cutting plane rounds      =  1
# of restarts                       =  0
# of nodes processed                =  0 (0.000s)
# of strong branching evaluations   =  0 (0.000s)
# of LP iterations                  =        1
# of function evaluations           =  0 (0.000s)
# of gradient evaluations           =  0 (0.000s)
# of hessian evaluations            =  0 (0.000s)
# of hessian-vector evaluations     =  0
# of subproblems processed          =  2 (0.000s)
Total program time (secs)           =  0.00124 (0.003 CPU time)
Time spent in evaluations (secs)    =  0.00000

Cuts statistics (gen / add)
---------------------------
Knapsack cuts                       =  0 / 0
Mixed-integer rounding cuts         =  2 / 0
Gomory cuts                         =  1 / 0
Flow-cover cuts                     =  0 / 0
Probing cuts                        =  4 / 2

Heuristics statistics (calls / successes / time)
------------------------------------------------
Rounding heuristic                  =  1 / 0 / 0.000s
Local search heuristic              =  4 / 4 / 0.000s

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

0

julia> KNITRO.KN_solve(kn)

=======================================
            Student License
       (NOT FOR COMMERCIAL USE)
         Artelys Knitro 15.0.0
=======================================

ERROR: Infeasible variable bound deduced from presolve.
       Variable: X0
       deduced upper bound =     0.00000000000000e+00 is less than
       deduced lower bound =     1.00000000000000e+00
-205

shell> cat bug.mps
*   PROBLEM IN MPS FORMAT
NAME          bug.mps
OBJSENSE
    MAX
ROWS
 N  OBJ
 L  C0
COLUMNS
    X0        OBJ       1
    X0        C0        1
    X1        C0        -0.1
RHS
    RHS       C0        0.999
RANGES
    RHS       C0        0.999
BOUNDS
 LI BND       X0        0
 UI BND       X0        2
 BV BND       X1
ENDATA

@odow
Copy link
Member Author

odow commented Jul 8, 2025

I'd also regard not accepting variable bounds for binary variables as a bug, particularly when it was supported in earlier versions.

I'll take a look at the second order cone errors.

@eminyouskn
Copy link
Collaborator

Thank you @odow for your detailed report. I encountered these issues as well and managed to find the source of the one related to solve_twice and the test_conic_SecondOrderCone_* tests. They’re both linked to Knitro’s presolve. I suggest we disable presolve for now. However, this doesn’t solve the problem with the binary variable bounds.

@eminyouskn
Copy link
Collaborator

By the way no need to change the bounds handeling for binary variables. I think it was a small bug in Knitro. We are going to fix it.

@odow odow mentioned this pull request Jul 9, 2025
@odow
Copy link
Member Author

odow commented Jul 9, 2025

We are going to fix it

Is there a timeline? If you plan to make a 15.1 patch release soon that fixes the binary variable and presolve bugs, then I would vote that we hold off merging this PR until it's ready.

@eminyouskn
Copy link
Collaborator

I don't have the exact timeline yet. I found the bounds issue and it's not hard to fix. However, the presolve issue seems very complicated. Let me check with the team and get back to you with an exact answer.

@odow
Copy link
Member Author

odow commented Jul 30, 2025

I updated to use KNITRO_jll, but because of the various bugs in presolve/binaries, I think I'm going to wait until [email protected] is released with a patch before working on this more.

@eminyouskn
Copy link
Collaborator

Hey @odow,

We have made a bugfix release 15.0.1 on pypi. I think we can start merging this PR and we can also update KNITRO_jll.jl with that release.

@odow odow merged commit 627eadb into master Sep 15, 2025
8 of 9 checks passed
@odow odow deleted the od/ci branch September 15, 2025 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Create a package KNITRO_jll.jl ?

3 participants