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

Solve error with v0.7.7 #168

Closed jbcaillau closed 3 months ago

jbcaillau commented 4 months ago

@ocots @PierreMartinon hi, can't solve anymore with v0.7.7 and julia 1.10.3 (see below). Any clue? IMG_3113

ocots commented 4 months ago

Qu'est-ce que j'ai encore fait ! Je vais checker ça asap.

ocots commented 4 months ago

Ok I see what happened. I fix this.

ocots commented 4 months ago

Actually, the solve function from OptimalControl.jl had init=nothing as default value:

function solve(ocp::OptimalControlModel, description::Symbol...; 
    display::Bool=__display(),
    init=nothing,
    kwargs...)

    #
    method = getFullDescription(description, available_methods())

    # print chosen method
    display ? println("Method = ", method) : nothing

    # if no error before, then the method is correct: no need of else
    if :direct ∈ method
        return CTDirect.solve(ocp, clean(method)...; display=display, init=init, kwargs...)
    end

end

I have changed the default value to: CTBase.OCPInit(). However, I am not sure that it is what we want.

In CTDirect.jl, in the solve function, we have:

"""
$(TYPEDSIGNATURES)

Solve an optimal control problem OCP by direct method
"""
function solve(ocp::OptimalControlModel,
    description...;
    init=OCPInit(),
    grid_size::Integer=__grid_size_direct(),
    display::Bool=__display(),
    print_level::Integer=__print_level_ipopt(),
    mu_strategy::String=__mu_strategy_ipopt(),
    kwargs...)

    # build init data if needed
    if !(init isa OCPInit)
        init = OCPInit(init)
    end     

    # build discretized OCP
    docp = directTranscription(ocp, description, init=init, grid_size=grid_size)

    # solve DOCP and retrieve OCP solution
    ocp_solution = solve(docp; display=display, print_level=print_level, mu_strategy=mu_strategy, kwargs...)

    return ocp_solution
end

and from a DOCP:

"""
$(TYPEDSIGNATURES)

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

    # solve DOCP with NLP solver
    print_level = display ?  print_level : 0
    if init == nothing
        docp_solution = ipopt(getNLP(docp), print_level=print_level, mu_strategy=mu_strategy, sb="yes"; kwargs...)
    else
        # build init data if needed
        if !(init isa OCPInit)
            init = OCPInit(init)
        end 
        docp_solution = ipopt(getNLP(docp),x0=DOCP_initial_guess(docp, init), print_level=print_level, mu_strategy=mu_strategy, sb="yes"; kwargs...)
    end

    # return solution for original OCP
    return OCPSolutionFromDOCP(docp, docp_solution)
end

So, when we solve from an OCP we are never in the case where init=nothing. However, if we solve from a DOCP, it possible. What is not clear is that we can provide an init when we do the transcription but it is not used if we do not provide an init to the solve.

"""
$(TYPEDSIGNATURES)

Discretize an optimal control problem into a nonlinear optimization problem (ie direct transcription)
"""
function directTranscription(ocp::OptimalControlModel,
    description...;
    init=OCPInit(),
    grid_size::Integer=__grid_size_direct())

    docp = DOCP(ocp, grid_size)

    # build init data if needed
    if !(init isa OCPInit)
        init = OCPInit(init)
    end 

    # set initial guess and bounds
    x0 = DOCP_initial_guess(docp, init)
    docp.var_l, docp.var_u = variables_bounds(docp)
    docp.con_l, docp.con_u = constraints_bounds(docp)

    # call NLP problem constructor
    docp.nlp = ADNLPModel!(x -> DOCP_objective(x, docp), 
                    x0,
                    docp.var_l, docp.var_u, 
                    (c, x) -> DOCP_constraints!(c, x, docp), 
                    docp.con_l, docp.con_u, 
                    backend = :optimized)

return docp

end
ocots commented 4 months ago

Maybe the right thing inside the DOCP solve:

if isnothing(init) && isnothing(getInit(docp))
    # we let the NLP solver provide the init
   docp_solution = ipopt(getNLP(docp), print_level=print_level, mu_strategy=mu_strategy, sb="yes"; kwargs...)
else 
   if isnothing(init) && !isnothing(getInit(docp))
       init = getInit(docp)
   end
   if !(init isa OCPInit)
       init = OCPInit(init)
   end
   docp_solution = ipopt(getNLP(docp),x0=DOCP_initial_guess(docp, init), print_level=print_level, mu_strategy=mu_strategy, sb="yes"; kwargs...)
end
ocots commented 4 months ago

Ok I didn't understand that the init is inside the model so we provide it if the solve method has no init. I propose something else.

ocots commented 4 months ago

Ok it is not so simple since ADNLPModel needs an initial guess.

ocots commented 4 months ago

I think we have to manage how we want. If we want to let the solver provide the init, we should separate in DOCP the init from the ADNLPModel, passing to ADNLPModel either the init if it is not nothing or a dummy init if init is nothing. I don't know if the ipopt function needs an init or not inside the nlp argument.

ocots commented 4 months ago

OK finalement c'est cohérent. Si on veut résoudre un docp on peut ne pas fournir d'init car le docp en a une. Dans ce cas par défaut init est nothing. Pour le reste il faut en fournir une et si l'utilisateur ne la donne pas on crée l'init par défaut. Du coup dans OptimalControl dans le solve on ne doit pas avoir nothing par défaut. Donc tout va bien. Il faudrait juste placer la construction d'init par défaut dans CTBase.jl.

PierreMartinon commented 4 months ago

Just in passing:

ocots commented 4 months ago

Ok thanks @PierreMartinon.

We have a file with the default values given by functions which return the default value. The functions can have inputs: https://github.com/control-toolbox/CTBase.jl/blob/main/src/default.jl

I have created the function which returns the default init in OptimalControl.jl:

https://github.com/control-toolbox/OptimalControl.jl/blob/6e52c4b4b88493d8a8770d6ebaa67da282f0db32/src/OptimalControl.jl#L25

This should be put in CTBase.jl.

Then, in the solver I have:

https://github.com/control-toolbox/OptimalControl.jl/blob/6e52c4b4b88493d8a8770d6ebaa67da282f0db32/src/solve.jl#L69

jbcaillau commented 4 months ago

@ocots @PierreMartinon hi, just had a quick look at your answers - thanks. so, are we back into a stable state with

jbcaillau commented 3 months ago

Continued here https://github.com/control-toolbox/CTDirect.jl/issues/115