# Functions for ODE-Solver: Radau5 and Radau

"""Name for Loading radau5 solver (64bit integers)."""
const DL_RADAU5 = "radau5"

"""Name for Loading radau5 solver (32bit integers)."""
const DL_RADAU5_I32 = "radau5_i32"

"""Name for Loading radau solver (64bit integers)."""
const DL_RADAU = "radau"

"""Name for Loading radau solver (32bit integers)."""
const DL_RADAU_I32 = "radau_i32"

"""macro for import Radau5 solver."""
macro import_radau5()
    return :(
        using ODEInterface: radau5, radau5_i32
    )
end

"""macro for import Radau solver."""
macro import_radau()
    return :(
        using ODEInterface: radau, radau_i32
    )
end

"""macro for import Radau5 dynamic lib names."""
macro import_DLradau5()
    return :(
        using ODEInterface: DL_RADAU5, DL_RADAU5_I32
    )
end

"""macro for import Radau dynamic lib names."""
macro import_DLradau()
    return :(
        using ODEInterface: DL_RADAU, DL_RADAU_I32
    )
end

"""macro for importing Radau5 help."""
macro import_radau5_help()
    return :(
        using ODEInterface: help_radau5_compile, help_radau5_license
    )
end

"""macro for importing Radau help."""
macro import_radau_help()
    return :(
        using ODEInterface: help_radau_compile, help_radau_license
    )
end

"""
Type encapsulating all required data for Radau5/Radau-Solver-Callbacks.

We have the typical calling stack:

     radau5/radau
         call_julia_output_fcn(  ... INIT ... )
             output_fcn ( ... INIT ...)
         ccall( RADAU5_/RADAU_ ... )
             unsafe_HW1MassCallback
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_HW2RHSCallback                      │  ⎬ cb. rhs
            │    rhs                                    │  ⎪
            └───────────────────────────────────────────┘  ⎭
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_radauSoloutCallback                 │  ⎪
            │    call_julia_output_fcn( ... STEP ...)   │  ⎪ cb. solout
            │        output_fcn ( ... STEP ...)         │  ⎬ with eval
            │            eval_sol_fcn                   │  ⎪
            │                ccall(CONTR5_/CONTRA_ ... )│  ⎪
            └───────────────────────────────────────────┘  ⎭
            ┌───────────────────────────────────────────┐  ⎫
            │unsafe_HW1JacCallback:                     │  ⎬ cb. jacobian
            │    call_julia_jac_fcn(             )      │  ⎪
            └───────────────────────────────────────────┘  ⎭
         call_julia_output_fcn(  ... DONE ... )
             output_fcn ( ... DONE ...)
"""
mutable struct RadauInternalCallInfos{
        FInt <: FortranInt, RHS_F,
        OUT_F, JAC_F,
    } <: ODEinternalCallInfos
    logio::IO                    # where to log
    loglevel::UInt64                # log level
    # special structure
    M1::FInt
    M2::FInt
    # RHS:
    rhs::RHS_F                 # right-hand-side
    rhs_mode::RHS_CALL_MODE         # how to call rhs
    rhs_lprefix::AbstractString        # saved log-prefix for rhs
    # SOLOUT & output function
    output_mode::OUTPUTFCN_MODE        # what mode for output function
    output_fcn::OUT_F                 # the output function to call
    output_data::Dict                  # extra_data for output_fcn
    out_lprefix::AbstractString        # saved log-prefix for solout
    eval_sol_fcn::Function              # eval_sol_fcn
    eval_lprefix::AbstractString        # saved log-prefix for eval_sol
    tOld::Float64               # tOld and
    tNew::Float64               # tNew and
    xNew::Vector{Float64}       # xNew of current solout interval
    cont_i::Vector{FInt}          # argument to contex
    cont_s::Vector{Float64}       # argument to contex
    cont_cont::Ptr{Float64}          # saved pointers for contr5
    cont_lrc::Ptr{FInt}             # saved pointers for contr5
    # Massmatrix
    massmatrix::AbstractArray{Float64} # saved mass matrix
    # Jacobimatrix
    jacobimatrix::JAC_F                 # function for jacobi matrix
    jacobibandstruct                      # (l,u) bandstructure or nothing
    jac_lprefix::AbstractString        # saved log-prefix for jac
end

"""
     type RadauArguments{FInt<:FortranInt} <:
              AbstractArgumentsODESolver{FInt}

Stores Arguments for Radau5 and Radau solver.

FInt is the Integer type used for the fortran compilation.
"""
mutable struct RadauArguments{FInt <: FortranInt} <: AbstractArgumentsODESolver{FInt}
    N::Vector{FInt}      # Dimension
    FCN::Ptr{Cvoid}        # rhs callback
    t::Vector{Float64}   # start time (and current)
    tEnd::Vector{Float64}   # end time
    x::Vector{Float64}   # initial value (and current state)
    H::Vector{Float64}   # initial step size
    RTOL::Vector{Float64}   # relative tolerance
    ATOL::Vector{Float64}   # absolute tolerance
    ITOL::Vector{FInt}      # switch for RTOL, ATOL
    JAC::Ptr{Cvoid}        # jacobian callback
    IJAC::Vector{FInt}      # jacobian given as callback?
    MLJAC::Vector{FInt}      # lower bandwidth of jacobian
    MUJAC::Vector{FInt}      # upper bandwidth of jacobian
    MAS::Ptr{Cvoid}
    IMAS::Vector{FInt}      # mass matrix given as callback?
    MLMAS::Vector{FInt}      # lower bandwidth of mass matrix
    MUMAS::Vector{FInt}      # upper bandwidth of mass matrix
    SOLOUT::Ptr{Cvoid}        # solout callback
    IOUT::Vector{FInt}      # switch for SOLOUT
    WORK::Vector{Float64}   # double working array
    LWORK::Vector{FInt}      # length of WORK
    IWORK::Vector{FInt}      # integer working array
    LIWORK::Vector{FInt}      # length of IWORK
    RPAR::Vector{Float64}   # add. double-array
    IPAR::Ref{RadauInternalCallInfos} # misuse IPAR
    IDID::Vector{FInt}      # Status code
    ## Allow uninitialized construction
    function RadauArguments{FInt}(dummy::FInt) where {FInt}
        return new{FInt}()
    end
end


"""
     function unsafe_radauSoloutCallback(
             nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
             x_::Ptr{Float64}, cont_::Ptr{Float64}, lrc_::Ptr{FInt},
             n_::Ptr{FInt}, rpar_::Ptr{Float64},
             cbi::CI, irtrn_::Ptr{FInt}) where {FInt<:FortranInt,
                                                CI<:RadauInternalCallInfos}

This is the solout given as callback to Fortran radau5/radau.

The `unsafe` prefix in the name indicates that no validations are
performed on the `Ptr`-pointers.

This function saves the state information of the solver in
`RadauInternalCallInfos`, where they can be found by
the `eval_sol_fcn`, see `create_radau_eval_sol_fcn_closure`.

Then the user-supplied `output_fcn` is called (which in turn can use
`eval_sol_fcn`, to evaluate the solution at intermediate points).

The return value of the `output_fcn` is propagated to `RADAU5_`/`RADAU_`.

For the typical calling sequence, see `RadauInternalCallInfos`.
"""
function unsafe_radauSoloutCallback(
        nr_::Ptr{FInt}, told_::Ptr{Float64}, t_::Ptr{Float64},
        x_::Ptr{Float64}, cont_::Ptr{Float64}, lrc_::Ptr{FInt},
        n_::Ptr{FInt}, rpar_::Ptr{Float64},
        cbi::CI, irtrn_::Ptr{FInt}
    ) where {
        FInt <: FortranInt,
        CI <: RadauInternalCallInfos,
    }

    nr = unsafe_load(nr_); told = unsafe_load(told_); t = unsafe_load(t_)
    n = unsafe_load(n_)
    x = unsafe_wrap(Array, x_, (n,), own = false)
    irtrn = unsafe_wrap(Array, irtrn_, (1,), own = false)

    (lio, l, lprefix) = (cbi.logio, cbi.loglevel, cbi.out_lprefix)
    l_sol = l & LOG_SOLOUT > 0

    l_sol && println(
        lio, lprefix, "called with nr=", nr, " told=", told,
        " t=", t, " x=", x
    )

    cbi.tOld = told; cbi.tNew = t; cbi.xNew = x
    cbi.cont_cont = cont_; cbi.cont_lrc = lrc_
    cbi.output_data["nr"] = nr

    ret = call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_STEP, told, t, x,
        cbi.eval_sol_fcn
    )

    if ret == OUTPUTFCN_RET_STOP
        irtrn[1] = -1
    elseif ret == OUTPUTFCN_RET_CONTINUE
        irtrn[1] = 0
    elseif ret == OUTPUTFCN_RET_CONTINUE_XCHANGED
        throw(
            FeatureNotSupported(
                string(
                    "Sorry; radau5/radau does not support ",
                    "to change the solution inside the output function."
                )
            )
        )
    else
        throw(InternalErrorODE(string("Unknown ret=", ret, " of output function")))
    end

    return nothing
end

"""
     function unsafe_radauSoloutCallback_c(cbi::CI,
             fint_flag::FInt) where {FInt,CI}
"""
function unsafe_radauSoloutCallback_c(
        cbi::CI,
        fint_flag::FInt
    ) where {FInt, CI}
    return @cfunction(
        unsafe_radauSoloutCallback, Cvoid, (
            Ptr{FInt},
            Ptr{Float64}, Ptr{Float64}, Ptr{Float64},
            Ptr{Float64}, Ptr{FInt}, Ptr{FInt},
            Ptr{Float64}, Ref{CI}, Ptr{FInt},
        )
    )
end

"""
     function create_radau_eval_sol_fcn_closure(cbi::CI, d::FInt,
             method_cont::Ptr{Cvoid}) where {FInt<:FortranInt,
                                            CI<:RadauInternalCallInfos}

generates a `eval_sol_fcn` for radau and radau5.

Why is a closure needed? We need a function `eval_sol_fcn`
that calls `CONTR5_` OR `CONTRA_` (with `ccall`).
But `CONTR5_`/`CONTRA_` need the information for the current state. This
information were saved by `unsafe_radauSoloutCallback` in the
`RadauInternalCallInfos`. `eval_sol_fcn` needs to get this information.
Here comes `create_radau_eval_sol_fcn_closure` into play: this function
takes call information and generates a `eval_sol_fcn` with this data.

Why doesn't `unsafe_radauSoloutCallback` generate a closure (then
the current state needs not to be saved in `RadauInternalCallInfos`)?
Because then every call to `unsafe_radauSoloutCallback` would have
generated a closure function. That's a lot of overhead: 1 closure function
for every solout call. With the strategy above, we have 1 closure function
per ODE-solver-call, i.e. 1 closure function per ODE.

For the typical calling sequence, see `RadauInternalCallInfos`.
"""
function create_radau_eval_sol_fcn_closure(
        cbi::CI, d::FInt,
        method_cont::Ptr{Cvoid}
    ) where {
        FInt <: FortranInt,
        CI <: RadauInternalCallInfos,
    }

    function eval_sol_fcn_closure(s::Float64)
        (lio, l, lprefix) = (cbi.logio, cbi.loglevel, cbi.eval_lprefix)
        l_eval = l & LOG_EVALSOL > 0

        l_eval && println(lio, lprefix, "called with s=", s)
        cbi.cont_s[1] = s
        result = Vector{Float64}(undef, d)
        if s == cbi.tNew
            result[:] = cbi.xNew
            l_eval && println(lio, lprefix, "not calling cont because s==tNew")
        else
            for k in 1:d
                cbi.cont_i[1] = k
                result[k] = ccall(
                    method_cont, Float64,
                    (Ptr{FInt}, Ptr{Float64}, Ptr{Float64}, Ptr{FInt}),
                    cbi.cont_i, cbi.cont_s, cbi.cont_cont, cbi.cont_lrc,
                )
            end
        end

        l_eval && println(lio, lprefix, "contr5 returned ", result)
        return result
    end
    return eval_sol_fcn_closure
end


"""
extracts options specific to radau5 and to radau.
Fills in `args`: `IWORK[1,2,4,5,6,7,8]`, `WORK[1,2,3,5,6]`,
`RPAR`, `IDID`, `FCN`, `SOLOUT`
"""
function extractCommonRadauOpt(
        d::FInt, T, t0, args::RadauArguments,
        opt::AbstractOptionsODE
    ) where {FInt <: FortranInt}
    OPT = nothing
    try
        # fill IWORK
        OPT = OPT_TRANSJTOH
        transjtoh = convert(Bool, getOption(opt, OPT, false))
        @assert (!transjtoh) ||
            ((!flag_jband) && (!flag_implct))  string(
            "Does not work, if the jacobian is banded or if the system ",
            " has a mass matrix ( ≠ Id)."
        )
        args.IWORK[1] = transjtoh ? 1 : 0

        OPT = OPT_MAXSTEPS; args.IWORK[2] = convert(FInt, getOption(opt, OPT, 100000))
        @assert 0 < args.IWORK[2]

        OPT = OPT_NEWTONSTARTZERO; zflag = convert(Bool, getOption(opt, OPT, false))
        args.IWORK[4] = zflag ? 1 : 0

        OPT = OPT_DIMOFIND1VAR; args.IWORK[5] = convert(FInt, getOption(opt, OPT, d))
        @assert 0 < args.IWORK[5]
        OPT = OPT_DIMOFIND2VAR; args.IWORK[6] = convert(FInt, getOption(opt, OPT, 0))
        @assert 0 ≤ args.IWORK[6]
        OPT = OPT_DIMOFIND3VAR; args.IWORK[7] = convert(FInt, getOption(opt, OPT, 0))
        @assert 0 ≤ args.IWORK[7]

        OPT = string(
            OPT_DIMOFIND1VAR, ", ", OPT_DIMOFIND2VAR, ", ",
            OPT_DIMOFIND3VAR
        )
        @assert(
            d == args.IWORK[5] + args.IWORK[6] + args.IWORK[7], string(
                "Sum of dim1, dim2 and dim3 variables must be the dimension of the ",
                "system."
            )
        )

        OPT = OPT_STEPSIZESTRATEGY
        args.IWORK[8] = convert(FInt, getOption(opt, OPT, 1))
        @assert args.IWORK[8] ∈ (1, 2)

        # fill WORK
        OPT = OPT_EPS; args.WORK[1] = convert(Float64, getOption(opt, OPT, 1.0e-16))
        @assert 1.0e-19 < args.WORK[1] < 1.0

        OPT = OPT_RHO
        args.WORK[2] = convert(Float64, getOption(opt, OPT, 0.9))
        @assert 0.001 < args.WORK[2] < 1.0

        OPT = OPT_JACRECOMPFACTOR
        args.WORK[3] = convert(Float64, getOption(opt, OPT, 0.001))
        @assert !(args.WORK[3] == 0)

        OPT = OPT_FREEZESSLEFT
        args.WORK[5] = convert(Float64, getOption(opt, OPT, 1.0))
        @assert args.WORK[5] ≤ 1.0

        OPT = OPT_FREEZESSRIGHT
        args.WORK[6] = convert(Float64, getOption(opt, OPT, 1.2))
        @assert args.WORK[6] ≥ 1.0

        OPT = OPT_MAXSS; args.WORK[7] = convert(Float64, getOption(opt, OPT, T - t0))
        @assert 0 ≠ args.WORK[7]

        OPT = OPT_SSMINSEL; args.WORK[8] = convert(Float64, getOption(opt, OPT, 0.2))
        @assert args.WORK[8] ≤ 1
        OPT = OPT_SSMAXSEL; args.WORK[9] = convert(Float64, getOption(opt, OPT, 8.0))
        @assert args.WORK[9] ≥ 1

        # H
        OPT = OPT_INITIALSS
        args.H = [convert(Float64, getOption(opt, OPT, 1.0e-6))]
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end
    args.RPAR = zeros(Float64, 0)
    args.IDID = zeros(FInt, 1)

    rhs_lprefix = "unsafe_HW2RHSCallback: "
    out_lprefix = "unsafe_radauSoloutCallback: "
    eval_lprefix = "eval_sol_fcn_closure: "

    return (rhs_lprefix, out_lprefix, eval_lprefix)
end

"""
calls the radau5 or radau solver after all solver arguments are prepared.
"""
function doRadauSolverCall(
        lio, l, l_g, l_solver, lprefix, d::FInt, M1::FInt, M2::FInt,
        rhs, rhs_mode, rhs_lprefix, output_mode, output_fcn, out_lprefix,
        eval_lprefix, massmatrix, jacobimatrix, jacobibandstruct,
        jac_lprefix, args, method_solver, method_cont
    ) where {FInt <: FortranInt}

    cbi = RadauInternalCallInfos(
        lio, l, M1, M2, rhs, rhs_mode, rhs_lprefix,
        output_mode, output_fcn,
        Dict(), out_lprefix, eval_sol_fcn_noeval, eval_lprefix,
        NaN, NaN, Vector{Float64}(),
        Vector{FInt}(undef, 1), Vector{Float64}(undef, 1),
        Ptr{Float64}(C_NULL), Ptr{FInt}(C_NULL),
        massmatrix === nothing ? zeros(0, 0) : massmatrix,
        jacobimatrix === nothing ? dummy_func : jacobimatrix,
        jacobibandstruct, jac_lprefix
    )

    if output_mode == OUTPUTFCN_DENSE
        cbi.eval_sol_fcn = create_radau_eval_sol_fcn_closure(cbi, d, method_cont)
    end

    args.FCN = unsafe_HW2RHSCallback_c(cbi, FInt(0))
    args.SOLOUT = output_mode ≠ OUTPUTFCN_NEVER ?
        unsafe_radauSoloutCallback_c(cbi, FInt(0)) :
        @cfunction(dummy_func, Cvoid, ())
    args.IPAR = cbi
    args.MAS = unsafe_HW1MassCallback_c(cbi, FInt(0))
    args.JAC = unsafe_HW1JacCallback_c(cbi, FInt(0))

    output_mode ≠ OUTPUTFCN_NEVER &&
        call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_INIT,
        args.t[1], args.tEnd[1], args.x, eval_sol_fcn_init
    ) # ignore result

    if l_solver
        println(lio, lprefix, "call Fortran radau5/radau $method_solver with")
        dump(lio, args)
    end

    ccall(
        method_solver, Cvoid,
        (
            Ptr{FInt}, Ptr{Cvoid},                     # N=d, Rightsidefunc
            Ptr{Float64}, Ptr{Float64}, Ptr{Float64},   # t, x, tEnd
            Ptr{Float64},                               # h
            Ptr{Float64}, Ptr{Float64}, Ptr{FInt},      # RTOL, ATOL, ITOL
            Ptr{Cvoid}, Ptr{FInt}, Ptr{FInt}, Ptr{FInt}, # JAC, IJAC, MLJAC, MUJAC
            Ptr{Cvoid}, Ptr{FInt}, Ptr{FInt}, Ptr{FInt}, # MAS, IJAC, MLJAC, MUJAC
            Ptr{Cvoid}, Ptr{FInt},                      # Soloutfunc, IOUT
            Ptr{Float64}, Ptr{FInt},                    # WORK, LWORK
            Ptr{FInt}, Ptr{FInt},                       # IWORK, LIWORK
            Ptr{Float64}, Ref{RadauInternalCallInfos},  # RPAR, IPAR
            Ptr{FInt},                                  # IDID
        ),
        args.N, args.FCN,
        args.t, args.x, args.tEnd,
        args.H,
        args.RTOL, args.ATOL, args.ITOL,
        args.JAC, args.IJAC, args.MLJAC, args.MUJAC,
        args.MAS, args.IMAS, args.MLMAS, args.MUMAS,
        args.SOLOUT, args.IOUT,
        args.WORK, args.LWORK,
        args.IWORK, args.LIWORK,
        args.RPAR, args.IPAR, args.IDID,
    )

    if l_solver
        println(lio, lprefix, "Fortran radau5/radau $method_solver returned")
        dump(lio, args)
    end

    output_mode ≠ OUTPUTFCN_NEVER &&
        call_julia_output_fcn(
        cbi, OUTPUTFCN_CALL_DONE,
        args.t[1], args.tEnd[1], args.x, eval_sol_fcn_done
    ) # ignore result

    l_g && println(lio, lprefix, string("done IDID=", args.IDID[1]))
    stats = Dict{AbstractString, Any}(
        "step_predict" => args.H[1],
        "no_rhs_calls" => args.IWORK[14],
        "no_jac_calls" => args.IWORK[15],
        "no_steps" => args.IWORK[16],
        "no_steps_accepted" => args.IWORK[17],
        "no_steps_rejected" => args.IWORK[18],
        "no_lu_decomp" => args.IWORK[19],
        "no_fw_bw_subst" => args.IWORK[20],
    )
    return (args.t[1], args.x, args.IDID[1], stats)
end

# Same documentation than for radau, is copied after
# the definition of the radau function.
function radau5(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return radau5_impl(rhs, t0, T, x0, opt, RadauArguments{Int64}(Int64(0)))
end

"""
radau5 with 32bit integers, see radau5.
"""
function radau5_i32(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return radau5_impl(rhs, t0, T, x0, opt, RadauArguments{Int32}(Int32(0)))
end

"""
     function radau5_impl(rhs,
             t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
             args::RadauArguments{FInt}) where FInt<:FortranInt

implementation of radau5 for FInt.
"""
function radau5_impl(
        rhs,
        t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
        args::RadauArguments{FInt}
    ) where {FInt <: FortranInt}

    (lio, l, l_g, l_solver, lprefix) = solver_start("radau5", rhs, t0, T, x0, opt)

    (method_radau5, method_contr5) = getAllMethodPtrs(
        (FInt == Int64) ? DL_RADAU5 : DL_RADAU5_I32
    )

    (d, nrdense, scalarFlag, rhs_mode, output_mode, output_fcn) =
        solver_extract_commonOpt(t0, T, x0, opt, args)

    args.ITOL = [scalarFlag ? 0 : 1]
    args.IOUT = [output_mode == OUTPUTFCN_NEVER ? 0 : 1]

    (M1, M2, NM1) = extractSpecialStructureOpt(d, opt)
    massmatrix = extractMassMatrix(M1, M2, NM1, args, opt)

    (jacobimatrix, jacobibandstruct, jac_lprefix) =
        extractJacobiOpt(d, M1, M2, NM1, args, opt)

    flag_implct = args.IMAS[1] ≠ 0
    flag_jband = args.MLJAC[1] < NM1

    # WORK memory
    ljac = flag_jband ? 1 + args.MLJAC[1] + args.MUJAC[1] : NM1
    lmas = (!flag_implct) ? 0 :
        (args.MLMAS[1] == NM1) ? NM1 : 1 + args.MLMAS[1] + args.MUMAS[1]
    le = flag_jband ? 1 + 2 * args.MLJAC[1] + args.MUJAC[1] : NM1

    args.LWORK = [
        (M1 == 0) ? d * (ljac + lmas + 3 * le + 12) + 20 :
            d * (ljac + 12) + NM1 * (lmas + 3 * le) + 20,
    ]
    args.WORK = zeros(Float64, args.LWORK[1])

    # IWORK memory
    args.LIWORK = [3 * d + 20]
    args.IWORK = zeros(FInt, args.LIWORK[1])

    args.IWORK[9] = M1; args.IWORK[10] = M2
    (rhs_lprefix, out_lprefix, eval_lprefix) =
        extractCommonRadauOpt(d, T, t0, args, opt)

    OPT = nothing
    try
        OPT = OPT_MAXNEWTONITER; args.IWORK[3] = convert(FInt, getOption(opt, OPT, 7))
        @assert 0 < args.IWORK[3]

        OPT = OPT_NEWTONSTOPCRIT
        args.WORK[4] = convert(
            Float64, getOption(
                opt, OPT,
                max(
                    10 * args.WORK[1] / args.RTOL[1],
                    min(0.03, sqrt(args.RTOL[1]))
                )
            )
        )
        @assert args.WORK[4] > args.WORK[1] / args.RTOL[1]
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end

    return doRadauSolverCall(
        lio, l, l_g, l_solver, lprefix, d, M1, M2,
        rhs, rhs_mode, rhs_lprefix, output_mode, output_fcn,
        out_lprefix, eval_lprefix, massmatrix,
        jacobimatrix, jacobibandstruct, jac_lprefix, args,
        method_radau5, method_contr5
    )
end

"""
     function radau(rhs, t0::Real, T::Real,
                     x0::Vector, opt::AbstractOptionsODE)
         -> (t,x,retcode,stats)

     function radau5(rhs, t0::Real, T::Real,
                     x0::Vector, opt::AbstractOptionsODE)
         -> (t,x,retcode,stats)

`retcode` can have the following values:

      1: computation successful
      2: computation. successful, but interrupted by output function
     -1: input is not consistent
     -2: larger OPT_MAXSTEPS is needed
     -3: step size becomes too small
     -4: matrix is repeatedly singular

main call for using Fortran radau or radau5 solver.

This solver support problems with special structure, see
`help_specialstructure`.

Remark:
Because radau and radau5 are collocation methods, there is no difference
in the computational costs for OUTPUTFCN_WODENSE and OUTPUTFCN_DENSE.

In `opt` the following options are used:

    ╔═════════════════╤══════════════════════════════════════════╤═════════╗
    ║  Option OPT_…   │ Description                              │ Default ║
    ╠═════════════════╪══════════════════════════════════════════╪═════════╣
    ║ M1 & M2         │ parameter for special structure, see     │       0 ║
    ║                 │ above                                    │      M1 ║
    ║                 │ M1, M2 ≥ 0                               │         ║
    ║                 │ M1 +M2 ≤ length(x0)                      │         ║
    ║                 │ (M1==M2==0) || (M1≠0≠M2)                 │         ║
    ║                 │ M1 % M2 == 0 or M1==0                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ RTOL         &  │ relative and absolute error tolerances   │    1e-3 ║
    ║ ATOL            │ both scalars or both vectors with the    │    1e-6 ║
    ║                 │ length of length(x0)                     │         ║
    ║                 │ error(xₖ) ≤ OPT_RTOLₖ⋅|xₖ|+OPT_ATOLₖ     │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OUTPUTFCN       │ output function                          │ nothing ║
    ║                 │ see help_outputfcn                       │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OUTPUTMODE      │ OUTPUTFCN_NEVER:                         │   NEVER ║
    ║                 │   dont't call OPT_OUTPUTFCN              │         ║
    ║                 │ OUTPUTFCN_WODENSE                        │         ║
    ║                 │   call OPT_OUTPUTFCN, but without        │         ║
    ║                 │   possibility for dense output           │         ║
    ║                 │ OUTPUTFCN_DENSE                          │         ║
    ║                 │   call OPT_OUTPUTFCN with support for    │         ║
    ║                 │   dense output                           │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ EPS             │ the rounding unit                        │   1e-16 ║
    ║                 │ 1e-19 < OPT_EPS < 1.0                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ TRANSJTOH       │ The solver transforms the jacobian       │   false ║
    ║                 │ matrix to Hessenberg form.               │         ║
    ║                 │ This option is not supported if the      │         ║
    ║                 │ system is "implicit" (i.e. a mass matrix │         ║
    ║                 │ is given) or if jacobian is banded.      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXNEWTONITER   │ maximum number of Newton iterations for  │       7 ║
    ║                 │ the solution of the implicit system in   │         ║
    ║                 │ each step.                               │         ║
    ║                 │ for radau: MAXNEWTONITER + (NS-3)*2.5    │         ║
    ║                 │   where NS is number of current stages   │         ║
    ║                 │ for radau5:     OPT_MAXNEWTONITER > 0    │         ║
    ║                 │ for radau: 50 > OPT_MAXNEWTONITER > 0    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ NEWTONSTARTZERO │ if `false`, the extrapolated collocation │   false ║
    ║                 │ solution is taken as starting vector for │         ║
    ║                 │ Newton's method. If `true` zero starting │         ║
    ║                 │ values are used. The latter is           │         ║
    ║                 │ recommended if Newton's method has       │         ║
    ║                 │ difficulties with convergence.           │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ NEWTONSTOPCRIT  │ only for radau5:                         │ see     ║
    ║                 │ Stopping criterion for Newton's method.  │   left  ║
    ║                 │ Smaller values make the code slower, but │         ║
    ║                 │ safer.                                   │         ║
    ║                 │ Default:                                 │         ║
    ║                 │  max(10*OPT_EPS/OPT_RTOL[1],             │         ║
    ║                 │       min(0.03,sqrt(OPT_RTOL[1])))       │         ║
    ║                 │ OPT_NEWTONSTOPCRIT > OPT_EPS/OPT_RTOL[1] │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ DIMOFIND1VAR  & │ For differential-algebraic systems of    │ len(x0) ║
    ║ DIMOFIND2VAR  & │ index > 1. The right-hand side should be │       0 ║
    ║ DIMOFIND3VAR    │ written such that the index 1,2,3        │       0 ║
    ║                 │ variables appear in this order.          │         ║
    ║                 │ DIMOFINDzVAR: number of index z vars.    │         ║
    ║                 │ ∑ DIMOFINDzVAR == length(x0)             │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSTEPS        │ maximal number of allowed steps          │  100000 ║
    ║                 │ OPT_MAXSTEPS > 0                         │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MAXSS           │ maximal step size                        │  T - t0 ║
    ║                 │ OPT_MAXSS ≠ 0                            │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ INITIALSS       │ initial step size guess                  │    1e-6 ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MINSTAGES     & │ only for radau:                          │       3 ║
    ║ MAXSTAGES       │ minimal and maximal number of stages.    │       7 ║
    ║                 │ The order is given by: 2⋅stages-1        │         ║
    ║                 │ MINSTAGES,MAXSTAGES ∈ (1,3,5,7)          │         ║
    ║                 │ MINSTAGES ≤ MAXSTAGES                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ INITSTAGES      │ only for radau:                          │MINSTAGES║
    ║                 │ number of stages to start with.          │         ║
    ║                 │ MINSTAGES ≤ INITSTAGES ≤ MAXSTAGES       │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ STEPSIZESTRATEGY│ Switch for step size strategy            │       1 ║
    ║                 │   1: mod. predictive controller          │         ║
    ║                 │      (Gustafsson)                        │         ║
    ║                 │   2: classical step size control         │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ OPT_RHO         │ safety factor for step control algorithm │     0.9 ║
    ║                 │ 0.001 < OPT_RHO < 1.0                    │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACRECOMPFACTOR │ decides whether the jacobian should be   │   0.001 ║
    ║                 │ recomputed.                              │         ║
    ║                 │ <0: recompute after every accepted step  │         ║
    ║                 │ small (≈ 0.001): recompute often         │         ║
    ║                 │ large (≈ 0.1): recompute rarely          │         ║
    ║                 │ i.e. this number represents how costly   │         ║
    ║                 │ Jacobia evaluations are.                 │         ║
    ║                 │ OPT_JACRECOMPFACTOR ≠ 0                  │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ FREEZESSLEFT  & │ Step size freezing: If                   │     1.0 ║
    ║ FREEZESSRIGHT   │ FREEZESSLEFT < hnew/hold < FREEZESSRIGHT │     1.2 ║
    ║                 │ then the step size is not changed. This  │         ║
    ║                 │ saves, together with a large             │         ║
    ║                 │ JACRECOMPFACTOR, LU-decompositions and   │         ║
    ║                 │ computing time for large systems.        │         ║
    ║                 │ small systems:                           │         ║
    ║                 │    FREEZESSLEFT  ≈ 1.0                   │         ║
    ║                 │    FREEZESSRIGHT ≈ 1.2                   │         ║
    ║                 │ large full systems:                      │         ║
    ║                 │    FREEZESSLEFT  ≈ 0.99                  │         ║
    ║                 │    FREEZESSRIGHT ≈ 2.0                   │         ║
    ║                 │                                          │         ║
    ║                 │ OPT_FREEZESSLEFT  ≤ 1.0                  │         ║
    ║                 │ OPT_FREEZESSRIGHT ≥ 1.0                  │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ SSMINSEL   &    │ parameters for step size selection       │     0.2 ║
    ║ SSMAXSEL        │ The new step size is chosen subject to   │     8.0 ║
    ║                 │ the restriction                          │         ║
    ║                 │ OPT_SSMINSEL ≤ hnew/hold ≤ OPT_SSMAXSEL  │         ║
    ║                 │ OPT_SSMINSEL ≤ 1, OPT_SSMAXSEL ≥ 1       │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ MASSMATRIX      │ the mass matrix of the problem. If not   │ nothing ║
    ║                 │ given (nothing) then the identity matrix  │         ║
    ║                 │ is used.                                 │         ║
    ║                 │ The size has to be (d-M1)×(d-M1).        │         ║
    ║                 │ It can be an full matrix or a banded     │         ║
    ║                 │ matrix (BandedMatrix).                   │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACOBIMATRIX    │ A function providing the Jacobian for    │ nothing ║
    ║                 │ ∂f/∂x or nothing. For nothing the solver │         ║
    ║                 │ uses finite differences to calculate the │         ║
    ║                 │ Jacobian.                                │         ║
    ║                 │ The function has to be of the form:      │         ║
    ║                 │   function (t,x,J) -> nothing       (A)  │         ║
    ║                 │ or for M1>0 & JACOBIBANDSTRUCT ≠ nothing │         ║
    ║                 │   function (t,x,J1,…,JK) -> nothing (B)  │         ║
    ║                 │ with K = 1+M1/M2 and (M1+M2==d)          │         ║
    ║                 │ see help_specialstructure                │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ JACOBIBANDSTRUCT│ A tuple (l,u) describing the banded      │ nothing ║
    ║                 │ structure of the Jacobian or nothing if  │         ║
    ║                 │ the Jacobian is full.                    │         ║
    ║                 │ see help_specialstructure                │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ ORDERDECFACTOR &│ only for radau:                          │     0.8 ║
    ║ ORDERINCFACTOR  │ Order is decreased, if the contractivity │   0.002 ║
    ║                 │ factor is smaller than ORDERDECFACTOR.   │         ║
    ║                 │ Order is increased, if the contractivity │         ║
    ║                 │ factor is larger than ORDERINCFACTOR.    │         ║
    ║                 │ ORDERDECFACTOR > ORDERINCFACTOR > 0      │         ║
    ╟─────────────────┼──────────────────────────────────────────┼─────────╢
    ║ ORDERDECSTEPFAC1│ only for radau:                          │     1.2 ║
    ║ ORDERDECSTEPFAC2│ the order is only decreased if the       │     0.8 ║
    ║                 │ stepsize ratio satisfies                 │         ║
    ║                 │  ORDERDECSTEPFAC2 ≤ hnew/hold ≤          │         ║
    ║                 │               ORDERDECSTEPFAC1           │         ║
    ║                 │ 0 < ORDERDECSTEPFAC2 < ORDERDECSTEPFAC1  │         ║
    ║                 │                                          │         ║
    ╚═════════════════╧══════════════════════════════════════════╧═════════╝
"""
function radau(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return radau_impl(rhs, t0, T, x0, opt, RadauArguments{Int64}(Int64(0)))
end

@doc(@doc(radau), radau5)

"""
radau with 32bit integers, see radau.
"""
function radau_i32(
        rhs, t0::Real, T::Real,
        x0::Vector, opt::AbstractOptionsODE
    )
    return radau_impl(rhs, t0, T, x0, opt, RadauArguments{Int32}(Int32(0)))
end

"""
     function radau_impl(rhs,
             t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
             args::RadauArguments{FInt}) where FInt<:FortranInt

implementation of radau for FInt.
"""
function radau_impl(
        rhs,
        t0::Real, T::Real, x0::Vector, opt::AbstractOptionsODE,
        args::RadauArguments{FInt}
    ) where {FInt <: FortranInt}

    (lio, l, l_g, l_solver, lprefix) = solver_start("radau5", rhs, t0, T, x0, opt)

    (method_radau, method_contra) = getAllMethodPtrs(
        (FInt == Int64) ? DL_RADAU : DL_RADAU_I32
    )

    (d, nrdense, scalarFlag, rhs_mode, output_mode, output_fcn) =
        solver_extract_commonOpt(t0, T, x0, opt, args)

    args.ITOL = [scalarFlag ? 0 : 1]
    args.IOUT = [output_mode == OUTPUTFCN_NEVER ? 0 : 1]

    (M1, M2, NM1) = extractSpecialStructureOpt(d, opt)
    massmatrix = extractMassMatrix(M1, M2, NM1, args, opt)

    (jacobimatrix, jacobibandstruct, jac_lprefix) =
        extractJacobiOpt(d, M1, M2, NM1, args, opt)

    flag_implct = args.IMAS[1] ≠ 0
    flag_jband = args.MLJAC[1] < NM1

    OPT = nothing
    NSMAX = 0
    try
        OPT = OPT_MAXSTAGES; NSMAX = convert(FInt, getOption(opt, OPT, 7))
        @assert NSMAX ∈ (1, 3, 5, 7)
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end

    # WORK memory
    ljac = flag_jband ? 1 + args.MLJAC[1] + args.MUJAC[1] : NM1
    lmas = (!flag_implct) ? 0 :
        (args.MLMAS[1] == NM1) ? NM1 : 1 + args.MLMAS[1] + args.MUMAS[1]
    le = flag_jband ? 1 + 2 * args.MLJAC[1] + args.MUJAC[1] : NM1

    args.LWORK = [
        (M1 == 0) ? d * (ljac + lmas + NSMAX * le + 3 * NSMAX + 3) + 20 :
            d * (ljac + 3 * NSMAX + 3) + NM1 * (lmas + NSMAX * le) + 20,
    ]
    args.WORK = zeros(Float64, args.LWORK[1])

    # IWORK memory
    args.LIWORK = [(2 + (NSMAX - 1) ÷ 2) * d + 20]
    args.IWORK = zeros(FInt, args.LIWORK[1])

    args.IWORK[9] = M1; args.IWORK[10] = M2
    (rhs_lprefix, out_lprefix, eval_lprefix) =
        extractCommonRadauOpt(d, T, t0, args, opt)

    try
        OPT = OPT_MAXNEWTONITER; args.IWORK[3] = convert(FInt, getOption(opt, OPT, 7))
        @assert 0 < args.IWORK[3] < 50

        OPT = OPT_MINSTAGES; NSMIN = convert(FInt, getOption(opt, OPT, 3))
        @assert NSMIN ∈ (1, 3, 5, 7) && NSMIN ≤ NSMAX
        args.IWORK[11] = NSMIN

        OPT = OPT_INITSTAGES; NSINIT = convert(FInt, getOption(opt, OPT, NSMIN))
        @assert NSINIT ∈ (1, 3, 5, 7) && NSMIN ≤ NSINIT ≤ NSMAX
        args.IWORK[13] = NSINIT

        OPT = OPT_ORDERINCFACTOR
        orderincfactor = convert(Float64, getOption(opt, OPT, 0.002))
        OPT = OPT_ORDERDECFACTOR
        orderdecfactor = convert(Float64, getOption(opt, OPT, 0.8))
        OPT = string(OPT_ORDERDECFACTOR, " & ", OPT_ORDERINCFACTOR)
        @assert 0 < orderincfactor < orderdecfactor
        args.WORK[10] = orderincfactor
        args.WORK[11] = orderdecfactor

        OPT = OPT_ORDERDECSTEPFAC1
        orderdecstepfac1 = convert(Float64, getOption(opt, OPT, 1.2))
        OPT = OPT_ORDERDECSTEPFAC2
        orderdecstepfac2 = convert(Float64, getOption(opt, OPT, 0.8))
        OPT = string(OPT_ORDERDECSTEPFAC1, " & ", OPT_ORDERDECSTEPFAC2)
        @assert 0 < orderdecstepfac2 < orderdecstepfac1
        args.WORK[12] = orderdecstepfac1
        args.WORK[13] = orderdecstepfac2
    catch e
        throw(ArgumentErrorODE("Option '$OPT': Not valid", :opt, e))
    end

    return doRadauSolverCall(
        lio, l, l_g, l_solver, lprefix, d, M1, M2,
        rhs, rhs_mode, rhs_lprefix, output_mode, output_fcn,
        out_lprefix, eval_lprefix, massmatrix,
        jacobimatrix, jacobibandstruct, jac_lprefix, args,
        method_radau, method_contra
    )
end

"""
## Compile RADAU5

The julia ODEInterface tries to compile and link the solvers
automatically at the build-time of this module. The following
calls need only be done, if one uses a different compiler and/or if
one wants to change/add some compiler options.

The Fortran source code can be found at:

     http://www.unige.ch/~hairer/software.html

See `help_radau5_license` for the license information.

### Using `gfortran` and 64bit integers (Linux and Mac)

Here is an example how to compile RADAU5 with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o radau5.o radau5.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o radau5.so
              radau5.o dc_lapack.o lapack.o lapackc.o
     gfortran -shared -fPIC -o radau5.dylib
              radau5.o dc_lapack.o lapack.o lapackc.o

### Using `gfortran` and 64bit integers (Windows)

Here is an example how to compile RADAU5 with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o radau5.o radau5.f

In order to get create a shared library (from the object file above) use

     gfortran -shared -o radau5.so
              radau5.o dc_lapack.o lapack.o lapackc.o

### Using `gfortran` and 32bit integers (Linux and Mac)

Here is an example how to compile RADAU5 with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o radau5_i32.o radau5.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o radau5_i32.so
               radau5_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o
     gfortran -shared -fPIC -o radau5_i32.dylib
               radau5_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

### Using `gfortran` and 32bit integers (Windows)

Here is an example how to compile RADAU5 with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o radau5_i32.o radau5.f

In order to get create a shared library (from the object file above) use:

     gfortran -shared -o radau5_i32.dll
               radau5_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

"""
function help_radau5_compile()
    return Docs.doc(help_radau5_compile)
end

function help_radau5_license()
    return Docs.doc(help_radau5_license)
end

@doc(@doc(hw_license), help_radau5_license)

"""
## Compile RADAU

The julia ODEInterface tries to compile and link the solvers
automatically at the build-time of this module. The following
calls need only be done, if one uses a different compiler and/or if
one wants to change/add some compiler options.

The Fortran source code can be found at:

     http://www.unige.ch/~hairer/software.html

See `help_radau_license` for the license information.

### Using `gfortran` and 64bit integers (Linux and Mac)

Here is an example how to compile RADAU with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fPIC -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o radau.o radau.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o radau.so
              radau.o dc_lapack.o lapack.o lapackc.o
     gfortran -shared -fPIC -o radau.dylib
              radau.o dc_lapack.o lapack.o lapackc.o

### Using `gfortran` and 64bit integers (Windows)

Here is an example how to compile RADAU with `Float64` reals and
`Int64` integers with `gfortran`:

     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o dc_lapack.o dc_lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapack.o lapack.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o lapackc.o lapackc.f
     gfortran -c -fdefault-integer-8
              -fdefault-real-8 -fdefault-double-8
              -o radau.o radau.f

In order to get create a shared library (from the object file above) use

     gfortran -shared -o radau.so
              radau.o dc_lapack.o lapack.o lapackc.o

### Using `gfortran` and 32bit integers (Linux and Mac)

Here is an example how to compile RADAU with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fPIC -fdefault-real-8 -fdefault-double-8
              -o radau_i32.o radau.f

In order to get create a shared library (from the object file above) use
one of the forms below (1st for Linux, 2nd for Mac):

     gfortran -shared -fPIC -o radau_i32.so
               radau_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o
     gfortran -shared -fPIC -o radau_i32.dylib
               radau_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

### Using `gfortran` and 32bit integers (Windows)

Here is an example how to compile RADAU with `Float64` reals and
`Int32` integers with `gfortran`:

     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o dc_lapack_i32.o dc_lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapack_i32.o lapack.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o lapackc_i32.o lapackc.f
     gfortran -c -fdefault-real-8 -fdefault-double-8
              -o radau_i32.o radau.f

In order to get create a shared library (from the object file above) use:

     gfortran -shared -o radau_i32.dll
               radau_i32.o dc_lapack_i32.o lapack_i32.o lapackc_i32.o

"""
function help_radau_compile()
    return Docs.doc(help_radau_compile)
end

function help_radau_license()
    return Docs.doc(help_radau_license)
end

@doc(@doc(hw_license), help_radau_license)

# Add information about solvers in global solverInfo-array.
push!(
    solverInfo,
    SolverInfo(
        "radau5",
        "Implicit Runge-Kutta method (Radau IIA) of order 5",
        tuple(
            :OPT_RTOL, :OPT_ATOL,
            :OPT_OUTPUTMODE, :OPT_OUTPUTFCN,
            :OPT_M1, :OPT_M2,
            :OPT_MASSMATRIX,
            :OPT_TRANSJTOH, :OPT_MAXSTEPS, :OPT_MAXNEWTONITER,
            :OPT_NEWTONSTARTZERO,
            :OPT_DIMOFIND1VAR, :OPT_DIMOFIND2VAR, :OPT_DIMOFIND3VAR,
            :OPT_STEPSIZESTRATEGY,
            :OPT_RHO, :OPT_JACRECOMPFACTOR, :OPT_NEWTONSTOPCRIT,
            :OPT_FREEZESSLEFT, :OPT_FREEZESSRIGHT,
            :OPT_SSMINSEL, :OPT_SSMAXSEL, :OPT_INITIALSS,
            :OPT_JACOBIMATRIX, :OPT_JACOBIBANDSTRUCT
        ),
        tuple(
            SolverVariant(
                "radau5_i64",
                "Radau5 with 64bit integers",
                DL_RADAU5,
                tuple("radau5", "contr5")
            ),
            SolverVariant(
                "radau5_i32",
                "Radau5 with 32bit integers",
                DL_RADAU5_I32,
                tuple("radau5", "contr5")
            ),
        ),
        help_radau5_compile,
        help_radau5_license,
    )
)

push!(
    solverInfo,
    SolverInfo(
        "radau",
        "Implicit Runge-Kutta method (Radau IIA) of variable order ∈ (5,9,13)",
        tuple(
            :OPT_RTOL, :OPT_ATOL,
            :OPT_OUTPUTMODE, :OPT_OUTPUTFCN,
            :OPT_M1, :OPT_M2,
            :OPT_MASSMATRIX,
            :OPT_TRANSJTOH, :OPT_MAXSTEPS, :OPT_MAXNEWTONITER,
            :OPT_NEWTONSTARTZERO,
            :OPT_DIMOFIND1VAR, :OPT_DIMOFIND2VAR, :OPT_DIMOFIND3VAR,
            :OPT_STEPSIZESTRATEGY,
            :OPT_RHO, :OPT_JACRECOMPFACTOR, :OPT_NEWTONSTOPCRIT,
            :OPT_FREEZESSLEFT, :OPT_FREEZESSRIGHT,
            :OPT_SSMINSEL, :OPT_SSMAXSEL, :OPT_INITIALSS,
            :OPT_JACOBIMATRIX, :OPT_JACOBIBANDSTRUCT,
            :OPT_MAXSTAGES, :OPT_MINSTAGES, :OPT_INITSTAGES,
            :OPT_ORDERDECFACTOR, :OPT_ORDERINCFACTOR,
            :OPT_ORDERDECSTEPFAC1, :OPT_ORDERDECSTEPFAC2
        ),
        tuple(
            SolverVariant(
                "radau_i64",
                "Radau with 64bit integers",
                DL_RADAU,
                tuple("radau", "contra")
            ),
            SolverVariant(
                "radau5_i32",
                "Radau with 32bit integers",
                DL_RADAU_I32,
                tuple("radau", "contra")
            ),
        ),
        help_radau_compile,
        help_radau_license,
    )
)

# vim:syn=julia:cc=79:fdm=indent:
