chkwon / PATHSolver.jl

provides a Julia wrapper for the PATH Solver for solving mixed complementarity problems
http://pages.cs.wisc.edu/%7Eferris/path.html
MIT License
51 stars 15 forks source link

Add error message when solve fails due to missing license - Feature Request #116

Closed mitchphillipson closed 4 months ago

mitchphillipson commented 4 months ago

Issue

The solver silently fails when a model has more than ~300 variables and no license. Since the PATH license needs to be updated on a yearly basis it's possible it expires unknowingly. This leads to many issues debugging models when the license has silently lapsed.

Proposed Solution

Throw an error when a model does not solve due to a license error.

odow commented 4 months ago

The termination_status(model) should be OTHER_ERROR, and raw_status(model) should be "License could not be found".

Is it not clear what is happening if you do solution_summary(model)?

odow commented 4 months ago

If you use the C API, then the return is MCP_LicenseError: https://github.com/chkwon/PATHSolver.jl/blob/d91e9b15418e9f81288946d306525b50337196a6/src/C_API.jl#L728

The general approach in MOI is to avoid erroring where possible on optimize! and return useful information via statuses, so I'm hesitant to make a change here.

mitchphillipson commented 4 months ago

The issue was in a GitHub test. One of the test cases was too large (which I didn't check, my fault). However, I am receiving a Segmentation Fault error.

image

I know this is in the call to optimize! because I've surrounded it in print statements, just to isolate. This is an example PR with the error. The error occurs in the test_orientation.jl test. I have observed that the test can't be isolated, if it's the only test it fails gracefully, as expected.

Getting a MWE is kind of tricky because it would involve changing my local workflow which is a little dangerous for me.

odow commented 4 months ago

Hmm. We shouldn't segfault. Let me take a look.

odow commented 4 months ago

This might actually be because of https://github.com/chkwon/PATHSolver.jl/pull/114.

Let me make a new release.

odow commented 4 months ago

Try v1.7.6.

I don't think this is a license error. Even on v1.7.5, we should have returned before we initialized the output:

https://github.com/chkwon/PATHSolver.jl/blob/d6fc14eb74698577db166cda5e14a02e6e0acc60/src/C_API.jl#L757-L762

mitchphillipson commented 4 months ago

That's interesting. I "solved" the error by including the license.

On Tue, May 28, 2024, 7:03 PM Oscar Dowson @.***> wrote:

Try v1.7.6.

I don't think this is a license error. Even on v1.7.5, we should have returned before we initialized the output:

https://github.com/chkwon/PATHSolver.jl/blob/d6fc14eb74698577db166cda5e14a02e6e0acc60/src/C_API.jl#L757-L762

— Reply to this email directly, view it on GitHub https://github.com/chkwon/PATHSolver.jl/issues/116#issuecomment-2136242805, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEVAPXPROUUMCNUZS47OOKTZEUEKNAVCNFSM6AAAAABIHWIBGSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZWGI2DEOBQGU . You are receiving this because you authored the thread.Message ID: @.***>

odow commented 4 months ago

That still could be consistent. Without the license we return early, and GC happens at a bad time. With the license, we solve, and the GC cleans things up okay. Dunno. I guess see if the new version fixes things first.

odow commented 4 months ago

Any update?

mitchphillipson commented 4 months ago

Sorry, I've been traveling. I have time today to test this.

mitchphillipson commented 4 months ago

I've updated PATHSolver to 1.7.6 and JuMP to 1.22.1 and still get a segfault here is a link to the output in GitHub, I think you're able to see this.

I'm a little constrained on time at the moment, but I'm going to try to get a local MWE of this issue. This may take me a some time. Luckily, this issue isn't pressing and seems to be a fairly edge case.

I'd be fine with this being a low priority, at least for you, until I can more accurately explain what makes this happen.

mitchphillipson commented 4 months ago

I'm back with testing and some strange behavior. For context, everything is up-to-date and I'm running locally without a license. If the first model I run fails due a license error, everything behaves as expected.

using JuMP
using PATHSolver

M2 = Model(PATHSolver.Optimizer)

I = Symbol.("a",1:500)
@variable(M2, x[I])
@constraint(M2, [i=I], x[i]^2-2*x[i]+1 ⟂ x[i])
optimize!(M2)

termination_status(M2)

This shows OTHER_ERROR::TerminationStatusCode = 24, which is expected.

However, if that model is NOT solved first, then we get unexpected behavior

using JuMP
using PATHSolver

M1 = Model(PATHSolver.Optimizer)
@variable(M1, x)
@constraint(M1, x^2-2*x+1 ⟂ x)

optimize!(M1)

M2 = Model(PATHSolver.Optimizer)

I = Symbol.("a",1:500)
@variable(M2, x[I])
@constraint(M2, [i=I], x[i]^2-2*x[i]+1 ⟂ x[i])
optimize!(M2)

termination_status(M2)

This has some variation. Either the model will hang on optimize!, fail to display any issues, or throw a helpful "Demo license: size restriction of 300 variables or 2000 nonzeros exceeded." message.

In the example above, the model hangs. To get the "Demo license" message I need to optimize the M2 model with fewer than 300 variables, then increase to above 300. Further testing shows this to be unreliable.

I also received this error once:

TypeError: in typeassert, expected PATHSolver.OutputData, got a value of type PATHSolver.Options(Ptr{Nothing} @0x0000020352eb3630)

which feels like a kinder segfault.

These errors all go away if I include the license. This could also be an error in PATH itself, perhaps it doesn't explicitly check for a license after the first solve but still fails due to lack of license on subsequent solves.

odow commented 4 months ago

Is this on Linux? Or Windows?

I cannot reproduce on Mac:

julia> using JuMP, PATHSolver

julia> begin
           model = Model(PATHSolver.Optimizer)
           @variable(model, x)
           @constraint(model, x^2 - 2 * x + 1 ⟂ x)
           optimize!(model)
       end
Path 5.0.03 (Fri Jun 26 09:58:07 2020)
Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris

Crash Log
major  func  diff  size  residual    step       prox   (label)
    0     0             1.0000e+00             0.0e+00 ()
    1     1     0     1 2.5000e-01  1.0e+00    0.0e+00 ()
pn_search terminated: no basis change.

Major Iteration Log
major minor  func  grad  residual    step  type prox    inorm  (label)
    0     0     2     2 2.5000e-01           I 0.0e+00 2.5e-01 ()
    1     1     3     3 6.2500e-02  1.0e+00 SO 0.0e+00 6.2e-02 ()
    2     1     4     4 1.5625e-02  1.0e+00 SO 0.0e+00 1.6e-02 ()
    3     1     5     5 3.9062e-03  1.0e+00 SO 0.0e+00 3.9e-03 ()
    4     1     6     6 9.7656e-04  1.0e+00 SO 0.0e+00 9.8e-04 ()
    5     1     7     7 2.4414e-04  1.0e+00 SO 0.0e+00 2.4e-04 ()
    6     1     8     8 6.1035e-05  1.0e+00 SO 0.0e+00 6.1e-05 ()
    7     1     9     9 1.5259e-05  1.0e+00 SO 0.0e+00 1.5e-05 ()
    8     1    10    10 3.8147e-06  1.0e+00 SO 0.0e+00 3.8e-06 ()
    9     1    11    11 9.5367e-07  1.0e+00 SO 0.0e+00 9.5e-07 ()

Major Iterations. . . . 9
Minor Iterations. . . . 9
Restarts. . . . . . . . 0
Crash Iterations. . . . 1
Gradient Steps. . . . . 0
Function Evaluations. . 11
Gradient Evaluations. . 11
Basis Time. . . . . . . 0.000022
Total Time. . . . . . . 0.001435
Residual. . . . . . . . 9.536743e-07

julia> begin
           model_2 = Model(PATHSolver.Optimizer)
           @variable(model_2, x[1:500])
           @constraint(model_2, [i in 1:500], x[i]^2 - 2 * x[i] + 1 ⟂ x[i])
           optimize!(model_2)
           termination_status(model_2)
       end
OTHER_ERROR::TerminationStatusCode = 24

julia> using JuMP, PATHSolver

julia> begin
           model = Model(PATHSolver.Optimizer)
           @variable(model, x)
           @constraint(model, x^2 - 2 * x + 1 ⟂ x)
           optimize!(model)
       end
Path 5.0.03 (Fri Jun 26 09:58:07 2020)
Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris

Crash Log
major  func  diff  size  residual    step       prox   (label)
    0     0             1.0000e+00             0.0e+00 ()
    1     1     0     1 2.5000e-01  1.0e+00    0.0e+00 ()
pn_search terminated: no basis change.

Major Iteration Log
major minor  func  grad  residual    step  type prox    inorm  (label)
    0     0     2     2 2.5000e-01           I 0.0e+00 2.5e-01 ()
    1     1     3     3 6.2500e-02  1.0e+00 SO 0.0e+00 6.2e-02 ()
    2     1     4     4 1.5625e-02  1.0e+00 SO 0.0e+00 1.6e-02 ()
    3     1     5     5 3.9062e-03  1.0e+00 SO 0.0e+00 3.9e-03 ()
    4     1     6     6 9.7656e-04  1.0e+00 SO 0.0e+00 9.8e-04 ()
    5     1     7     7 2.4414e-04  1.0e+00 SO 0.0e+00 2.4e-04 ()
    6     1     8     8 6.1035e-05  1.0e+00 SO 0.0e+00 6.1e-05 ()
    7     1     9     9 1.5259e-05  1.0e+00 SO 0.0e+00 1.5e-05 ()
    8     1    10    10 3.8147e-06  1.0e+00 SO 0.0e+00 3.8e-06 ()
    9     1    11    11 9.5367e-07  1.0e+00 SO 0.0e+00 9.5e-07 ()

Major Iterations. . . . 9
Minor Iterations. . . . 9
Restarts. . . . . . . . 0
Crash Iterations. . . . 1
Gradient Steps. . . . . 0
Function Evaluations. . 11
Gradient Evaluations. . 11
Basis Time. . . . . . . 0.000017
Total Time. . . . . . . 0.001030
Residual. . . . . . . . 9.536743e-07

julia> begin
           model_2 = Model(PATHSolver.Optimizer)
           @variable(model_2, x[1:500])
           @constraint(model_2, [i in 1:500], x[i]^2 - 2 * x[i] + 1 ⟂ x[i])
           optimize!(model_2)
           termination_status(model_2)
       end
OTHER_ERROR::TerminationStatusCode = 24

julia> using JuMP, PATHSolver

julia> begin
           model = Model(PATHSolver.Optimizer)
           @variable(model, x)
           @constraint(model, x^2 - 2 * x + 1 ⟂ x)
           optimize!(model)
       end
Path 5.0.03 (Fri Jun 26 09:58:07 2020)
Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris

Crash Log
major  func  diff  size  residual    step       prox   (label)
    0     0             1.0000e+00             0.0e+00 ()
    1     1     0     1 2.5000e-01  1.0e+00    0.0e+00 ()
pn_search terminated: no basis change.

Major Iteration Log
major minor  func  grad  residual    step  type prox    inorm  (label)
    0     0     2     2 2.5000e-01           I 0.0e+00 2.5e-01 ()
    1     1     3     3 6.2500e-02  1.0e+00 SO 0.0e+00 6.2e-02 ()
    2     1     4     4 1.5625e-02  1.0e+00 SO 0.0e+00 1.6e-02 ()
    3     1     5     5 3.9062e-03  1.0e+00 SO 0.0e+00 3.9e-03 ()
    4     1     6     6 9.7656e-04  1.0e+00 SO 0.0e+00 9.8e-04 ()
    5     1     7     7 2.4414e-04  1.0e+00 SO 0.0e+00 2.4e-04 ()
    6     1     8     8 6.1035e-05  1.0e+00 SO 0.0e+00 6.1e-05 ()
    7     1     9     9 1.5259e-05  1.0e+00 SO 0.0e+00 1.5e-05 ()
    8     1    10    10 3.8147e-06  1.0e+00 SO 0.0e+00 3.8e-06 ()
    9     1    11    11 9.5367e-07  1.0e+00 SO 0.0e+00 9.5e-07 ()

Major Iterations. . . . 9
Minor Iterations. . . . 9
Restarts. . . . . . . . 0
Crash Iterations. . . . 1
Gradient Steps. . . . . 0
Function Evaluations. . 11
Gradient Evaluations. . 11
Basis Time. . . . . . . 0.000019
Total Time. . . . . . . 0.001248
Residual. . . . . . . . 9.536743e-07

julia> begin
           model_2 = Model(PATHSolver.Optimizer)
           @variable(model_2, x[1:500])
           @constraint(model_2, [i in 1:500], x[i]^2 - 2 * x[i] + 1 ⟂ x[i])
           optimize!(model_2)
           termination_status(model_2)
       end
OTHER_ERROR::TerminationStatusCode = 24

julia> using JuMP, PATHSolver

julia> begin
           model = Model(PATHSolver.Optimizer)
           @variable(model, x)
           @constraint(model, x^2 - 2 * x + 1 ⟂ x)
           optimize!(model)
       end
Path 5.0.03 (Fri Jun 26 09:58:07 2020)
Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris

Crash Log
major  func  diff  size  residual    step       prox   (label)
    0     0             1.0000e+00             0.0e+00 ()
    1     1     0     1 2.5000e-01  1.0e+00    0.0e+00 ()
pn_search terminated: no basis change.

Major Iteration Log
major minor  func  grad  residual    step  type prox    inorm  (label)
    0     0     2     2 2.5000e-01           I 0.0e+00 2.5e-01 ()
    1     1     3     3 6.2500e-02  1.0e+00 SO 0.0e+00 6.2e-02 ()
    2     1     4     4 1.5625e-02  1.0e+00 SO 0.0e+00 1.6e-02 ()
    3     1     5     5 3.9062e-03  1.0e+00 SO 0.0e+00 3.9e-03 ()
    4     1     6     6 9.7656e-04  1.0e+00 SO 0.0e+00 9.8e-04 ()
    5     1     7     7 2.4414e-04  1.0e+00 SO 0.0e+00 2.4e-04 ()
    6     1     8     8 6.1035e-05  1.0e+00 SO 0.0e+00 6.1e-05 ()
    7     1     9     9 1.5259e-05  1.0e+00 SO 0.0e+00 1.5e-05 ()
    8     1    10    10 3.8147e-06  1.0e+00 SO 0.0e+00 3.8e-06 ()
    9     1    11    11 9.5367e-07  1.0e+00 SO 0.0e+00 9.5e-07 ()

Major Iterations. . . . 9
Minor Iterations. . . . 9
Restarts. . . . . . . . 0
Crash Iterations. . . . 1
Gradient Steps. . . . . 0
Function Evaluations. . 11
Gradient Evaluations. . 11
Basis Time. . . . . . . 0.000020
Total Time. . . . . . . 0.001310
Residual. . . . . . . . 9.536743e-07

julia> begin
           model_2 = Model(PATHSolver.Optimizer)
           @variable(model_2, x[1:500])
           @constraint(model_2, [i in 1:500], x[i]^2 - 2 * x[i] + 1 ⟂ x[i])
           optimize!(model_2)
           termination_status(model_2)
       end
OTHER_ERROR::TerminationStatusCode = 24

julia> using JuMP, PATHSolver

julia> begin
           model = Model(PATHSolver.Optimizer)
           @variable(model, x)
           @constraint(model, x^2 - 2 * x + 1 ⟂ x)
           optimize!(model)
       end
Path 5.0.03 (Fri Jun 26 09:58:07 2020)
Written by Todd Munson, Steven Dirkse, Youngdae Kim, and Michael Ferris

Crash Log
major  func  diff  size  residual    step       prox   (label)
    0     0             1.0000e+00             0.0e+00 ()
    1     1     0     1 2.5000e-01  1.0e+00    0.0e+00 ()
pn_search terminated: no basis change.

Major Iteration Log
major minor  func  grad  residual    step  type prox    inorm  (label)
    0     0     2     2 2.5000e-01           I 0.0e+00 2.5e-01 ()
    1     1     3     3 6.2500e-02  1.0e+00 SO 0.0e+00 6.2e-02 ()
    2     1     4     4 1.5625e-02  1.0e+00 SO 0.0e+00 1.6e-02 ()
    3     1     5     5 3.9062e-03  1.0e+00 SO 0.0e+00 3.9e-03 ()
    4     1     6     6 9.7656e-04  1.0e+00 SO 0.0e+00 9.8e-04 ()
    5     1     7     7 2.4414e-04  1.0e+00 SO 0.0e+00 2.4e-04 ()
    6     1     8     8 6.1035e-05  1.0e+00 SO 0.0e+00 6.1e-05 ()
    7     1     9     9 1.5259e-05  1.0e+00 SO 0.0e+00 1.5e-05 ()
    8     1    10    10 3.8147e-06  1.0e+00 SO 0.0e+00 3.8e-06 ()
    9     1    11    11 9.5367e-07  1.0e+00 SO 0.0e+00 9.5e-07 ()

Major Iterations. . . . 9
Minor Iterations. . . . 9
Restarts. . . . . . . . 0
Crash Iterations. . . . 1
Gradient Steps. . . . . 0
Function Evaluations. . 11
Gradient Evaluations. . 11
Basis Time. . . . . . . 0.000026
Total Time. . . . . . . 0.001290
Residual. . . . . . . . 9.536743e-07

julia> begin
           model_2 = Model(PATHSolver.Optimizer)
           @variable(model_2, x[1:500])
           @constraint(model_2, [i in 1:500], x[i]^2 - 2 * x[i] + 1 ⟂ x[i])
           optimize!(model_2)
           termination_status(model_2)
       end
OTHER_ERROR::TerminationStatusCode = 24

(path) pkg> st
Status `/private/tmp/path/Project.toml`
  [4076af6c] JuMP v1.22.1
  [f5f7c340] PATHSolver v1.7.6
odow commented 4 months ago

TypeError: in typeassert, expected PATHSolver.OutputData, got a value of type PATHSolver.Options(Ptr{Nothing} @0x0000020352eb3630)

I don't understand this at all.

PATH is passing the pointer of the options struct to the print method, instead of the output data.

That seems like a bug in PATH, not in PATHSolver.jl

mitchphillipson commented 4 months ago

I'm on windows.

A bug in PATH is the most likely.

On Tue, Jun 11, 2024, 5:31 PM Oscar Dowson @.***> wrote:

TypeError: in typeassert, expected PATHSolver.OutputData, got a value of type PATHSolver.Options(Ptr{Nothing} @0x0000020352eb3630)

I don't understand this at all.

PATH is passing the pointer of the options struct to the print method, instead of the output data.

That seems like a bug in PATH, not in PATHSolver.jl

— Reply to this email directly, view it on GitHub https://github.com/chkwon/PATHSolver.jl/issues/116#issuecomment-2161709264, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEVAPXJPYSKILXL2XBP5AH3ZG53E3AVCNFSM6AAAAABIHWIBGSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNRRG4YDSMRWGQ . You are receiving this because you authored the thread.Message ID: @.***>

odow commented 4 months ago

This doesn't explain your CI failure, but I guess it's related. I assume the answer is just don't forget the license.

mitchphillipson commented 4 months ago

I'll email Michael Ferris this thread with a bit more info. He might be interested.

WalterLu3 commented 4 months ago

Hello. I am Michael's student. I will create an issue about this in the internal PATH codebase for you. But first, could you try something for me? I saw that you call c_api_Path_CheckLicense in the solve_mcp routine, and I was wondering if you could make this check be done after you set the c_api_Output_SetInterface. This is just a wild shot because c_api_Path_CheckLicense will print out messages for the demo license limit if violated, but it requires the output_interface to be set already. I think maybe the fact that the pointer to the print function PATH calls in the output_interface is not set can lead to some weird behaviors.

mitchphillipson commented 4 months ago

This does appear to solve the issues I've been having. I've opened PR and linked this issue.

odow commented 4 months ago

because c_api_Path_CheckLicense will print out messages for the demo license limit if violated, but it requires the output_interface to be set already.

Ahhhhhh. I didn't consider this. This makes sense

odow commented 4 months ago

Hah. I obviously ran into this at some point, and then forgot about it:

https://github.com/chkwon/PATHSolver.jl/blob/cbb4b8a81fae90fe2b27611d9b20a1680d208929/test/C_API.jl#L23-L45

odow commented 4 months ago

I should have asked Michael at the time, but I guess I forgot about this.

Fixed the test: https://github.com/chkwon/PATHSolver.jl/pull/120