Implement an MPS-based MOI solver

I want to implement an MPS-based solver interface.
The actual solver is on a server and I have to send an MPS file, get the solution back and update the JuMP model with the solution.
Are there any existing packages that are a good starting point? I have seen NEOS.jl but it is pretty outdated.
So far I know how to get an MPS file from a JuMP model using MathOptFormat.jl.
Do I have to define the rest of the MOI wrapper functions as in e.g. CPLEX.jl?

3 Likes

Yes, I believe you have to implement some MOI methods, e.g., for querying the status and solution values etc. But you don’t have to accept all different kinds of function/set pairs for constraints, if they can be handled by bridges. And you also don’t need to support incremental model building or model updates.

The MOI docs have a section about implementing solver interfaces for solvers that only accept full models. This would be most appropriate for your case, I guess.

1 Like

If you plan to use MathOptFormat to write the file, you will need to load the problem into MathOptFormat.MPS.InnerModel so you might as well do

struct Optimizer
    mps_model::MathOptFormat.MPS.InnerModel{Float64}
end

and redirect all model building methods to mps_model. Then in MOI.optimize! you can call write_to_file, get the result and stor it in Optimizer.
To redirect model building methods you can do for instance

MOI.supports_constraint(optimizer::Optimizer, F, S) = MOI.supports_constraint(optimizer.mps_model, F, S)
MOI.add_constraint(optimizer::Optimizer, func, set) = MOI.add_constraint(optimizer.mps_model, func, set)
MOI.Utilities.supports_default_copy_to(::Optimizer, ::Bool) = true
...

Alternatively, you can only implement MOI.copy_to and create the MathOptFormat.MPS.InnerModel inside this function as @leethargo suggested but that means that your solver won’t be usable with JuMP direct mode and will use an addictional cache for the bridged model in JuMP automatic and manual model.

1 Like

@blegat’s suggestion is good. A work-flow like the following?

struct MyOptimizer <: MOI.AbstractOptimizer
    inner::MathOptFormat.MPS.Model
    solution_cache # You will need to sort this out
end
MOI.supports_constraint(optimizer::Optimizer, F, S) = MOI.supports_constraint(optimizer.mps_model, F, S)
MOI.add_constraint(optimizer::Optimizer, func, set) = MOI.add_constraint(optimizer.mps_model, func, set)
MOI.Utilities.supports_default_copy_to(::Optimizer, ::Bool) = true

function MOI.optimize!(model::MyOptimizer)
    MOI.write_to_file(model.inner, "model.mps")
    send_model_to_server("model.mps")
    sol_file = get_sol_file_from_server()
    model.solution_cache = parse_sol_file(model.inner, sol_file)
    return
end

function MOI.get(model::MyOptimizer, ::MOI.VariablePrimal(), x::MOI.VariableIndex)
    return model.solution_cache[x] # Or something
end

This is very close to what I’ll do with NEOS.jl. Is it possible to make the send_file_to_server modular so that we can swap out NEOS for your server? That would save some code.

2 Likes

You will probably need to define lots more methods, like add_variable and some supports. So anytime you see a MethodError it probably means to need to forward that method from MyOptimizer to the inner MPS.Model.

1 Like