control-toolbox / OptimalControl.jl

Model and solve optimal control problems in Julia
http://control-toolbox.org/OptimalControl.jl/
MIT License
69 stars 6 forks source link

Common solve #173

Closed ocots closed 3 months ago

ocots commented 4 months ago

If we use NonlinearSolve to solve nonlinear equations and OptimalControl to solve an OCP in the same script, then there an ambiguity with both solve method. We need to overload the solve from CommonSolve I guess.

PierreMartinon commented 4 months ago

Did you try it ? The arguments are different, so Julia should be able to match the proper method (?)

ocots commented 4 months ago

I haven't try but yes I think that with multiple dispatch it will work fine. Otherwise, we have to prefix the method solve.

PierreMartinon commented 4 months ago

Ok, I tried with CTdirect, we need to prefix

using CTDirect
using NonlinearSolve
include("ocp.jl") # defines ocp1
sol = solve(ocp1)

fails with

WARNING: both NonlinearSolve and CTDirect export "solve"; uses of it in module Main must be qualified

Calling CTDirect.solve(ocp1) works fine.

ocots commented 4 months ago

That's why we need to share the same function. I think we should take a look at the CommonSolve.jl package.

PierreMartinon commented 3 months ago

That's why we need to share the same function. I think we should take a look at the CommonSolve.jl package.

Did you try it ? Their documentation is ... quite brief. And not that clear. Maybe we should look for an example.

ocots commented 3 months ago

@PierreMartinon First example:

Capture d’écran 2024-06-26 à 13 46 29

Note. I don't know why I have exported MySolve. This is useless I guess.

ocots commented 3 months ago

With another package that export a solve function. Note that the solve function extends the one from CommonSolve.

Capture d’écran 2024-06-26 à 13 47 53

ocots commented 3 months ago

With CommonSolve.jl:

Capture d’écran 2024-06-26 à 13 52 22

PierreMartinon commented 3 months ago

Thanks, I'll have a look. I just finished the package extensions for CTDirect.

PierreMartinon commented 3 months ago

@ocots Ok, halfway done I guess. I managed to redefine CommonSolve.solve and use it to solve a DOCP, but the export is not done properly, ie I have to prefix and call

CommonSolve.solve(docp)

I tried the export both in the extension and CTDirect, do you know what I'm missing ?

module CTSolveExt

using CTDirect
using CTBase
using CommonSolve
using DocStringExtensions

using NLPModelsIpopt
using HSL

"""
$(TYPEDSIGNATURES)

Solve a discretized optimal control problem DOCP
"""
function CommonSolve.solve(docp::DOCP;
    init=nothing,
    display::Bool=CTDirect.__display(),
    print_level::Integer=CTDirect.__print_level_ipopt(),
    mu_strategy::String=CTDirect.__mu_strategy_ipopt(),
    linear_solver::String=CTDirect.__linear_solver(),
    kwargs...)

    # Linear solver
    if (linear_solver == "ma27") || (linear_solver == "ma57") || (linear_solver == "ma77") || (linear_solver == "ma86") || (linear_solver == "ma97")
        if !LIBHSL_isfunctional()
            linear_solver = "mumps"
        end
    end
    if linear_solver == "spral"
        if !haskey(ENV, "OMP_CANCELLATION") || !haskey(ENV, "OMP_PROC_BIND")
            linear_solver = "mumps"
        end
    end

    # solve DOCP with NLP solver
    print_level = display ?  print_level : 0
    if init == nothing
        # use initial guess embedded in the DOCP
        docp_solution = ipopt(getNLP(docp), print_level=print_level, mu_strategy=mu_strategy, sb="yes", linear_solver=linear_solver; kwargs...)
    else
        # use given initial guess
        docp_solution = ipopt(getNLP(docp), x0=CTDirect.DOCP_initial_guess(docp, OptimalControlInit(init)), print_level=print_level, mu_strategy=mu_strategy, sb="yes", linear_solver=linear_solver; kwargs...)
    end

    # return DOCP solution
    return docp_solution
end

export solve

end
PierreMartinon commented 3 months ago

Ok, it works now oO FWIW I used in CTDirect

using CommonSolve: solve
export solve

Now I have to do the same with solve(ocp), that actually calls solve(docp) internally...

ocots commented 3 months ago

Since you redefine it into an extension it changes some things. I have to think about it. I don't know the answer.

ocots commented 3 months ago

I think that the solve function must be defined into CTDirect.jl and this function may call an internal one defined in both CTDirect.jl and the extension. In CTDirect.jl this internal function should have as arguments args...; kwargs... and return an error. In the extension the internal function is redefine with the right signature and do the job.

The export is only in CTDirect.jl.

ocots commented 3 months ago

Like the Flow function here:

https://github.com/control-toolbox/CTFlows.jl/blob/main/src%2FCTFlows.jl#L26

PierreMartinon commented 3 months ago

The export in the extension can indeed be removed.

Apparently the fact that we redefine the solve function from CommonSolve is incompatible with declaring an 'empty' solve in CTDirect. I get a compilation error if I try.

In the other extension CTDirectExt I did use empty functions in CTDirect. Insome cases I got the precompilation error when redefining methods. As a result I had to change the signatures for the read/load functions, which is a bit ugly.

save_OCP_solution() = error("placeholder for save")
#load_OCP_solution() = error("placeholder for load") # precompilation error (overwriting method)
function load_OCP_solution end #ok for precompilation
export_OCP_solution() = error("placeholder for export")
function read_OCP_solution end

By the way, people seem to disagree on how exactly the 'empty' functions should be written. I guess the feature is still new.

ocots commented 3 months ago

That's why I said to define an internal function which redefined in the extension. The solve function is defined once.

ocots commented 3 months ago

I think that it has been done by @PierreMartinon.