From 57934d1a25957e55b537ec34ebb16b450dd830fc Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 16 Apr 2021 00:48:30 -0300 Subject: [PATCH 01/10] Update logger formatting and tests --- src/logger.jl | 51 +++++++++++++++++++++++++++----------------------- test/logger.jl | 9 +++++++++ 2 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 test/logger.jl diff --git a/src/logger.jl b/src/logger.jl index ed157e5..1f5696a 100644 --- a/src/logger.jl +++ b/src/logger.jl @@ -1,17 +1,20 @@ export log_header, log_row -const formats = Dict{DataType, String}(Signed => "%6d", - AbstractFloat => "%8.1e", - AbstractString => "%15s", - Symbol => "%15s", - Missing => "%15s" - ) - -const default_headers = Dict{Symbol, String}(:name => "Name", - :elapsed_time => "Time", - :objective => "f(x)", - :dual_feas => "Dual", - :primal_feas => "Primal") +const formats = Dict{DataType, String}( + Signed => "%6d", + AbstractFloat => "%8.1e", + AbstractString => "%15s", + Symbol => "%15s", + Missing => "%15s", +) + +const default_headers = Dict{Symbol, String}( + :name => "Name", + :elapsed_time => "Time", + :objective => "f(x)", + :dual_feas => "Dual", + :primal_feas => "Primal", +) for (typ, fmt) in formats hdr_fmt_foo = Symbol("header_formatter_$typ") @@ -19,11 +22,11 @@ for (typ, fmt) in formats fmt2 = "%$(len)s" @eval begin - row_formatter(x :: $typ) = @sprintf($fmt, x) - row_formatter(:: Type{<:$typ}) = @sprintf($fmt2, "-") + row_formatter(x::$typ) = @sprintf($fmt, x) + row_formatter(::Type{<:$typ}) = @sprintf($fmt2, "-") $hdr_fmt_foo(x) = @sprintf($fmt2, x) - header_formatter(x :: Union{Symbol,String}, :: Type{<:$typ}) = $hdr_fmt_foo(x) + header_formatter(x::Union{Symbol, String}, ::Type{<:$typ}) = $hdr_fmt_foo(x) end end @@ -44,11 +47,13 @@ Keyword arguments: See also [`log_row`](@ref). """ -function log_header(colnames :: AbstractVector{Symbol}, coltypes :: AbstractVector{DataType}; - hdr_override :: Dict{Symbol,String} = Dict{Symbol,String}(), - colsep :: Int = 2, - ) - out = "" +function log_header( + colnames::AbstractVector{Symbol}, + coltypes::AbstractVector{DataType}; + hdr_override::Dict{Symbol, String} = Dict{Symbol, String}(), + colsep::Int = 2, +) + out_vec = String[] for (name, typ) in zip(colnames, coltypes) x = if haskey(hdr_override, name) hdr_override[name] @@ -57,9 +62,9 @@ function log_header(colnames :: AbstractVector{Symbol}, coltypes :: AbstractVect else string(name) end - out *= header_formatter(x, typ) * " "^colsep + push!(out_vec, header_formatter(x, typ)) end - return out + return join(out_vec, " "^colsep) end """ @@ -82,7 +87,7 @@ Prints Keyword arguments: - `colsep::Int`: Number of spaces between columns (Default: 2) """ -function log_row(vals; colsep :: Int = 2) +function log_row(vals; colsep::Int = 2) string_cols = (row_formatter(val) for val in vals) return join(string_cols, " "^colsep) end diff --git a/test/logger.jl b/test/logger.jl new file mode 100644 index 0000000..339589a --- /dev/null +++ b/test/logger.jl @@ -0,0 +1,9 @@ +@testset "Logger" begin + @info "Testing logger" + s = log_header([:col_real, :col_int, :col_symbol, :col_string], [Float64, Int, Symbol, String]) + @test s == @sprintf("%8s %6s %15s %15s", "col_real", "col_int", "col_symbol", "col_string") + s = log_row([1.0, 1, :one, "one"]) + @test s == @sprintf("%8.1e %6d %15s %15s", 1.0, 1, "one", "one") + s = log_row([Float64, Int, Symbol, String]) + @test s == @sprintf("%8s %6s %15s %15s", "-", "-", "-", "-") +end From 4653ee59a3e05fb854b8f7c094e315284680a666 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 16 Apr 2021 00:50:15 -0300 Subject: [PATCH 02/10] Solver revamp src folder --- Project.toml | 8 +- src/SolverCore.jl | 12 ++- src/grid-search-tuning.jl | 100 ++++++++++++++++++ src/output.jl | 36 +++++++ src/parameters.jl | 46 +++++++++ src/solver.jl | 26 +++++ src/stats.jl | 212 -------------------------------------- src/traits.jl | 19 ++++ 8 files changed, 239 insertions(+), 220 deletions(-) create mode 100644 src/grid-search-tuning.jl create mode 100644 src/output.jl create mode 100644 src/parameters.jl create mode 100644 src/solver.jl delete mode 100644 src/stats.jl create mode 100644 src/traits.jl diff --git a/Project.toml b/Project.toml index 58c81f1..078bd2e 100644 --- a/Project.toml +++ b/Project.toml @@ -3,18 +3,18 @@ uuid = "ff4d7338-4cf1-434d-91df-b86cb86fb843" version = "0.1.0" [deps] -NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" [compat] -NLPModels = "0.14" julia = "^1.3.0" [extras] -ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["ADNLPModels", "LinearAlgebra", "Logging", "Test"] +test = ["LinearAlgebra", "Logging", "Random", "Test"] diff --git a/src/SolverCore.jl b/src/SolverCore.jl index 2674c34..49bc3aa 100644 --- a/src/SolverCore.jl +++ b/src/SolverCore.jl @@ -1,12 +1,16 @@ module SolverCore # stdlib -using Printf +using Logging, Printf +using OrderedCollections -# our packages -using NLPModels +include("solver.jl") +include("output.jl") include("logger.jl") -include("stats.jl") +include("parameters.jl") +include("traits.jl") + +include("grid-search-tuning.jl") end diff --git a/src/grid-search-tuning.jl b/src/grid-search-tuning.jl new file mode 100644 index 0000000..5dd5e41 --- /dev/null +++ b/src/grid-search-tuning.jl @@ -0,0 +1,100 @@ +export grid_search_tune + +# TODO: Issue: For grid_search_tune to work, we need to define `reset!`, but LinearOperators also define reset! +function reset! end + +# TODO: Decide success and costs of grid_search_tune below + +""" + solver, results = grid_search_tune(SolverType, problems; kwargs...) + +Simple tuning of solver `SolverType` by grid search, on `problems`, which should be iterable. +The following keyword arguments are available: +- `success`: A function to be applied on a solver output that returns whether the problem has terminated succesfully. Defaults to `o -> o.status == :first_order`. +- `costs`: A vector of cost functions and penalties. Each element is a tuple of two elements. The first is a function to be applied to the output of the solver, and the second is the cost when the solver fails (see `success` above) or throws an error. Defaults to +``` +[ + (o -> o.elapsed_time, 100.0), + (o -> o.counters.neval_obj + o.counters.neval_cons, 1000), + (o -> !success(o), 1), +] +``` +which represent the total elapsed_time (with a penalty of 100.0 for failures); the number of objective and constraints functions evaluations (with a penalty of 1000 for failures); and the number of failures. +- `grid_length`: The number of points in the ranges of the grid for continuous points. +- `solver_kwargs`: Arguments to be passed to the solver. Note: use this to set the stopping parameters, but not the other parameters being optimize. +- Any parameters accepted by the `Solver`: a range to be used instead of the default range. + +The default ranges are based on the parameters types, and are as follows: +- `:real`: linear range from `:min` to `:max` with `grid_length` points. +- `:log`: logarithmic range from `:min` to `:max` with `grid_length` points. Computed by exp of linear range of `log(:min)` to `log(:max)`. +- `:bool`: either `false` or `true`. +- `:int`: integer range from `:min` to `:max`. +""" +function grid_search_tune( + ::Type{Solver}, + problems; + success = o -> o.status == :first_order, + costs = [(o -> o.elapsed_time, 100.0), (o -> !success(o), 1)], + grid_length = 10, + solver_kwargs = Dict(), + kwargs..., +) where {Solver <: AbstractSolver} + solver_params = parameters(Solver) + params = OrderedDict() + for (k, v) in pairs(solver_params) + if v[:type] <: AbstractFloat && (!haskey(v, :scale) || v[:scale] == :linear) + params[k] = LinRange(v[:min], v[:max], grid_length) + elseif v[:type] <: AbstractFloat && v[:scale] == :log + params[k] = exp.(LinRange(log(v[:min]), log(v[:max]), grid_length)) + elseif v[:type] == Bool + params[k] = (false, true) + elseif v[:type] <: Integer + params[k] = v[:min]:v[:max] + end + end + for (k, v) in kwargs + params[k] = v + end + + # Precompiling + problem = first(problems) + try + solver = Solver(problem) + output = with_logger(NullLogger()) do + solve!(solver, problem) + end + finally + finalize(problem) + end + + cost(θ) = begin + total_cost = [zero(x[2]) for x in costs] + for problem in problems + reset!(problem) + try + solver = Solver(problem) + P = (k => θi for (k, θi) in zip(keys(solver_params), θ)) + output = with_logger(NullLogger()) do + solve!(solver, problem; P...) + end + for (i, c) in enumerate(costs) + if success(output) + total_cost[i] += (c[1])(output) + else + total_cost[i] += c[2] + end + end + catch ex + for (i, c) in enumerate(costs) + total_cost[i] += c[2] + end + @error ex + finally + finalize(problem) + end + end + total_cost + end + + [θ => cost(θ) for θ in Iterators.product(values(params)...)] +end diff --git a/src/output.jl b/src/output.jl new file mode 100644 index 0000000..1251a73 --- /dev/null +++ b/src/output.jl @@ -0,0 +1,36 @@ +export AbstractSolverOutput + +# TODO: Define the required fields and API for all Outputs +""" + AbstractSolverOutput{T} + +Base type for output of JSO-compliant solvers. +An output must have at least the following: +- `status :: Symbol` +- `solution` +""" +abstract type AbstractSolverOutput{T} end + +# TODO: Decision: Should STATUSES be fixed? Should it be all here? +const STATUSES = Dict( + :exception => "unhandled exception", + :first_order => "first-order stationary", + :acceptable => "solved to within acceptable tolerances", + :infeasible => "problem may be infeasible", + :max_eval => "maximum number of function evaluations", + :max_iter => "maximum iteration", + :max_time => "maximum elapsed time", + :neg_pred => "negative predicted reduction", + :not_desc => "not a descent direction", + :small_residual => "small residual", + :small_step => "step too small", + :stalled => "stalled", + :unbounded => "objective function may be unbounded from below", + :unknown => "unknown", + :user => "user-requested stop", +) + +function Base.show(io::IO, output::AbstractSolverOutput) + println(io, "Solver output of type $(typeof(output))") + println(io, "Status: $(STATUSES[output.status])") +end diff --git a/src/parameters.jl b/src/parameters.jl new file mode 100644 index 0000000..ddb53ed --- /dev/null +++ b/src/parameters.jl @@ -0,0 +1,46 @@ +export parameters, are_valid_parameters + +""" + named_tuple = parameters(solver) + named_tuple = parameters(SolverType) + named_tuple = parameters(SolverType{T}) + +Return the parameters of a `solver`, or of the type `SolverType`. +You can specify the type `T` of the `SolverType`. +The returned structure is a nested NamedTuple. +Each key of `named_tuple` is the name of a parameter, and its value is a NamedTuple containing +- `default`: The default value of the parameter. +- `type`: The type of the parameter, such as `Int`, `Float64`, `T`, etc. + +and possibly other values depending on the `type`. +Some possibilies are: + +- `scale`: How to explore the domain + - `:linear`: A continuous value within a range + - `:log`: A positive continuous value that should be explored logarithmically (like 10⁻², 10⁻¹, 1, 10). +- `min`: Minimum value. +- `max`: Maximum value. + +Solvers should define + + SolverCore.parameters(::Type{Solver{T}}) where T +""" +function parameters end + +parameters(::Type{S}) where {S <: AbstractSolver} = parameters(S{Float64}) +parameters(::S) where {S <: AbstractSolver} = parameters(S) + +""" + are_valid_parameters(solver, args...) + +Return whether the parameters given in `args` are valid for `solver`. +The order of the parameters must be the same as in `parameters(solver)`. + +Solvers should define + + SolverCore.are_valid_parameters(::Type{Solver{T}}, arg1, arg2, ...) where T +""" +function are_valid_parameters end +are_valid_parameters(::Type{S}, args...) where {S <: AbstractSolver} = + are_valid_parameters(S{Float64}, args...) +are_valid_parameters(::S, args...) where {S <: AbstractSolver} = are_valid_parameters(S, args...) diff --git a/src/solver.jl b/src/solver.jl new file mode 100644 index 0000000..1921294 --- /dev/null +++ b/src/solver.jl @@ -0,0 +1,26 @@ +export AbstractSolver, solve! + +# TODO: Define the required fields and API for all Solvers +""" + AbstractSolver + +Base type for JSO-compliant solvers. +A solver must have three members: +- `initialized :: Bool`, indicating whether the solver was initialized +- `params :: Dict`, a dictionary of parameters for the solver +- `workspace`, a named tuple with arrays used by the solver. +""" +abstract type AbstractSolver{T} end + +function Base.show(io::IO, solver::AbstractSolver) + println(io, "Solver $(typeof(solver))") +end + +""" + output = solve!(solver, problem) + +Solve `problem` with `solver`. +""" +function solve! end + +# TODO: Define general constructors that automatically call `solve!`, etc.? diff --git a/src/stats.jl b/src/stats.jl deleted file mode 100644 index 483cb9e..0000000 --- a/src/stats.jl +++ /dev/null @@ -1,212 +0,0 @@ -export AbstractExecutionStats, GenericExecutionStats, - statsgetfield, statshead, statsline, getStatus, show_statuses - -const STATUSES = Dict( - :exception => "unhandled exception", - :first_order => "first-order stationary", - :acceptable => "solved to within acceptable tolerances", - :infeasible => "problem may be infeasible", - :max_eval => "maximum number of function evaluations", - :max_iter => "maximum iteration", - :max_time => "maximum elapsed time", - :neg_pred => "negative predicted reduction", - :not_desc => "not a descent direction", - :small_residual => "small residual", - :small_step => "step too small", - :stalled => "stalled", - :unbounded => "objective function may be unbounded from below", - :unknown => "unknown", - :user => "user-requested stop", - ) - -""" - show_statuses() - -Show the list of available statuses to use with `GenericExecutionStats`. -""" -function show_statuses() - println("STATUSES:") - for k in keys(STATUSES) |> collect |> sort - v = STATUSES[k] - @printf(" :%-14s => %s\n", k, v) - end -end - -abstract type AbstractExecutionStats end - -""" - GenericExecutionStats(status, nlp; ...) - -A GenericExecutionStats is a struct for storing output information of solvers. -It contains the following fields: -- `status`: Indicates the output of the solver. Use `show_statuses()` for the full list; -- `solution`: The final approximation returned by the solver (default: `[]`); -- `objective`: The objective value at `solution` (default: `Inf`); -- `dual_feas`: The dual feasibility norm at `solution` (default: `Inf`); -- `primal_feas`: The primal feasibility norm at `solution` (default: `0.0` if uncontrained, `Inf` otherwise); -- `multipliers`: The Lagrange multiplers wrt to the constraints (default: `[]`); -- `multipliers_L`: The Lagrange multiplers wrt to the lower bounds on the variables (default: `[]`); -- `multipliers_U`: The Lagrange multiplers wrt to the upper bounds on the variables (default: `[]`); -- `iter`: The number of iterations computed by the solver (default: `-1`); -- `elapsed_time`: The elapsed time computed by the solver (default: `Inf`); -- `counters::NLPModels.NLSCounters`: The Internal structure storing the number of functions evaluations; -- `solver_specific::Dict{Symbol,Any}`: A solver specific dictionary. - -The `counters` variable is a copy of `nlp`'s counters, and `status` is mandatory on construction. -All other variables can be input as keyword arguments. - -Notice that `GenericExecutionStats` does not compute anything, it simply stores. -""" -mutable struct GenericExecutionStats <: AbstractExecutionStats - status :: Symbol - solution :: Vector # x - objective :: Real # f(x) - dual_feas :: Real # ‖∇f(x)‖₂ for unc, ‖P[x - ∇f(x)] - x‖₂ for bnd, etc. - primal_feas :: Real # ‖c(x)‖ for equalities - multipliers :: Vector # y - multipliers_L :: Vector # zL - multipliers_U :: Vector # zU - iter :: Int - counters :: NLPModels.NLSCounters - elapsed_time :: Real - solver_specific :: Dict{Symbol,Any} -end - -function GenericExecutionStats(status :: Symbol, - nlp :: AbstractNLPModel; - solution :: Vector=eltype(nlp.meta.x0)[], - objective :: Real=eltype(solution)(Inf), - dual_feas :: Real=eltype(solution)(Inf), - primal_feas :: Real=unconstrained(nlp) || bound_constrained(nlp) ? zero(eltype(solution)) : eltype(solution)(Inf), - multipliers :: Vector=eltype(nlp.meta.x0)[], - multipliers_L :: Vector=eltype(nlp.meta.x0)[], - multipliers_U :: Vector=eltype(nlp.meta.x0)[], - iter :: Int=-1, - elapsed_time :: Real=Inf, - solver_specific :: Dict{Symbol,T}=Dict{Symbol,Any}()) where {T} - if !(status in keys(STATUSES)) - @error "status $status is not a valid status. Use one of the following: " join(keys(STATUSES), ", ") - throw(KeyError(status)) - end - c = NLSCounters() - for counter in fieldnames(Counters) - setfield!(c.counters, counter, eval(Meta.parse("$counter"))(nlp)) - end - if nlp isa AbstractNLSModel - for counter in fieldnames(NLSCounters) - counter == :counters && continue - setfield!(c, counter, eval(Meta.parse("$counter"))(nlp)) - end - end - return GenericExecutionStats(status, solution, objective, dual_feas, primal_feas, - multipliers, multipliers_L, multipliers_U, iter, - c, elapsed_time, solver_specific) -end - -import Base.show, Base.print, Base.println - -function show(io :: IO, stats :: AbstractExecutionStats) - show(io, "Execution stats: $(getStatus(stats))") -end - -# TODO: Expose NLPModels dsp in nlp_types.jl function print -function disp_vector(io :: IO, x :: Vector) - if length(x) == 0 - print(io, "∅") - elseif length(x) <= 5 - Base.show_delim_array(io, x, "[", " ", "]", false) - else - Base.show_delim_array(io, x[1:4], "[", " ", "", false) - print(io, " ⋯ $(x[end])]") - end -end - -function print(io :: IO, stats :: GenericExecutionStats; showvec :: Function=disp_vector) - # TODO: Show evaluations - println(io, "Generic Execution stats") - println(io, " status: " * getStatus(stats)) - println(io, " objective value: ", stats.objective) - println(io, " primal feasibility: ", stats.primal_feas) - println(io, " dual feasibility: ", stats.dual_feas) - print(io, " solution: "); showvec(io, stats.solution); println(io, "") - length(stats.multipliers) > 0 && (print(io, " multipliers: "); showvec(io, stats.multipliers); println(io, "")) - length(stats.multipliers_L) > 0 && (print(io, " multipliers_L: "); showvec(io, stats.multipliers_L); println(io, "")) - length(stats.multipliers_U) > 0 && (print(io, " multipliers_U: "); showvec(io, stats.multipliers_U); println(io, "")) - println(io, " iterations: ", stats.iter) - println(io, " elapsed time: ", stats.elapsed_time) - if length(stats.solver_specific) > 0 - println(io, " solver specific:") - for (k,v) in stats.solver_specific - @printf(io, " %s: ", k) - if v isa Vector - showvec(io, v) - else - show(io, v) - end - println(io, "") - end - end -end -print(stats :: GenericExecutionStats; showvec :: Function=disp_vector) = - print(Base.stdout, stats, showvec=showvec) -println(io :: IO, stats :: GenericExecutionStats; showvec :: - Function=disp_vector) = print(io, stats, showvec=showvec) -println(stats :: GenericExecutionStats; showvec :: Function=disp_vector) = - print(Base.stdout, stats, showvec=showvec) - -const headsym = Dict(:status => " Status", - :iter => " Iter", - :neval_obj => " #obj", - :neval_grad => " #grad", - :neval_cons => " #cons", - :neval_jcon => " #jcon", - :neval_jgrad => " #jgrad", - :neval_jac => " #jac", - :neval_jprod => " #jprod", - :neval_jtprod => "#jtprod", - :neval_hess => " #hess", - :neval_hprod => " #hprod", - :neval_jhprod => "#jhprod", - :objective => " f", - :dual_feas => " ‖∇f‖", - :elapsed_time => " Elaspsed time") - -function statsgetfield(stats :: AbstractExecutionStats, name :: Symbol) - t = Int - if name == :status - v = getStatus(stats) - t = String - elseif name in fieldnames(NLPModels.NLSCounters) - v = getfield(stats.counters, name) - elseif name in fieldnames(NLPModels.Counters) - if stats.counters isa NLPModels.Counters - v = getfield(stats.counters, name) - else - v = getfield(stats.counters.counters, name) - end - elseif name in fieldnames(typeof(stats)) - v = getfield(stats, name) - t = fieldtype(typeof(stats), name) - else - error("Unknown field $name") - end - if t <: Int - @sprintf("%7d", v) - elseif t <: Real - @sprintf("%15.8e", v) - else - @sprintf("%8s", v) - end -end - -function statshead(line :: Array{Symbol}) - return join([headsym[x] for x in line], " ") -end - -function statsline(stats :: AbstractExecutionStats, line :: Array{Symbol}) - return join([statsgetfield(stats, x) for x in line], " ") -end - -function getStatus(stats :: AbstractExecutionStats) - return STATUSES[stats.status] -end diff --git a/src/traits.jl b/src/traits.jl new file mode 100644 index 0000000..3258a9d --- /dev/null +++ b/src/traits.jl @@ -0,0 +1,19 @@ +export problem_types_handled, can_solve_type + +# TODO: Nothing is decided for traits +""" + problem_types_handled(solver) + +List the problem types handled by the `solver`. +""" +problem_types_handled(::Type{<:AbstractSolver}) = [] + +""" + can_solve_type(solver, type) + +Check if the `solver` can solve problems of `type`. +Call `problem_types_handled` for a list of problem types that the `solver` can solve. +""" +function can_solve_type(::Type{S}, t::Symbol) where {S <: AbstractSolver} + return t ∈ problem_types_handled(S) +end From 7477b0c7a0c46f4cab0d455d06d15072b42b147a Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 16 Apr 2021 00:50:33 -0300 Subject: [PATCH 03/10] Solver revamp test folder --- test/dummy_solver.jl | 78 --------------------- test/output.jl | 14 ++++ test/root-finding.jl | 157 +++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 12 ++-- test/solver.jl | 37 ++++++++++ test/test_logging.jl | 17 ----- test/test_stats.jl | 96 -------------------------- 7 files changed, 213 insertions(+), 198 deletions(-) delete mode 100644 test/dummy_solver.jl create mode 100644 test/output.jl create mode 100644 test/root-finding.jl create mode 100644 test/solver.jl delete mode 100644 test/test_logging.jl delete mode 100644 test/test_stats.jl diff --git a/test/dummy_solver.jl b/test/dummy_solver.jl deleted file mode 100644 index faa8cbc..0000000 --- a/test/dummy_solver.jl +++ /dev/null @@ -1,78 +0,0 @@ -function dummy_solver( - nlp :: AbstractNLPModel; - x :: AbstractVector = nlp.meta.x0, - atol :: Real = sqrt(eps(eltype(x))), - rtol :: Real = sqrt(eps(eltype(x))), - max_eval :: Int = 1000, - max_time :: Float64 = 30.0, -) - - start_time = time() - elapsed_time = 0.0 - - nvar, ncon = nlp.meta.nvar, nlp.meta.ncon - T = eltype(x) - - cx = ncon > 0 ? cons(nlp, x) : zeros(T, 0) - gx = grad(nlp, x) - Jx = ncon > 0 ? jac(nlp, x) : zeros(T, 0, nvar) - y = -Jx' \ gx - Hxy = ncon > 0 ? hess(nlp, x, y) : hess(nlp, x) - - dual = gx + Jx' * y - - iter = 0 - - ϵd = atol + rtol * norm(dual) - ϵp = atol - - fx = obj(nlp, x) - @info log_header([:iter, :f, :c, :dual, :t], [Int, T, T, Float64]) - @info log_row(Any[iter, fx, norm(cx), norm(dual), elapsed_time]) - solved = norm(dual) < ϵd && norm(cx) < ϵp - tired = neval_obj(nlp) + neval_cons(nlp) > max_eval || elapsed_time > max_time - - while !(solved || tired) - Hxy = ncon > 0 ? hess(nlp, x, y) : hess(nlp, x) - W = Symmetric([Hxy zeros(T, nvar, ncon); Jx zeros(T, ncon, ncon)], :L) - Δxy = -W \ [dual; cx] - Δx = Δxy[1:nvar] - Δy = Δxy[nvar+1:end] - x += Δx - y += Δy - - cx = ncon > 0 ? cons(nlp, x) : zeros(T, 0) - gx = grad(nlp, x) - Jx = ncon > 0 ? jac(nlp, x) : zeros(T, 0, nvar) - dual = gx + Jx' * y - elapsed_time = time() - start_time - solved = norm(dual) < ϵd && norm(cx) < ϵp - tired = neval_obj(nlp) + neval_cons(nlp) > max_eval || elapsed_time > max_time - - iter += 1 - fx = obj(nlp, x) - @info log_row(Any[iter, fx, norm(cx), norm(dual), elapsed_time]) - end - - status = if solved - :first_order - elseif elapsed_time > max_time - :max_time - else - :max_eval - end - - return GenericExecutionStats( - :unknown, - nlp, - objective=fx, - dual_feas=norm(dual), - primal_feas=norm(cx), - multipliers=y, - multipliers_L=zeros(T, nvar), - multipliers_U=zeros(T, nvar), - elapsed_time=elapsed_time, - solution=x, - iter=iter - ) -end diff --git a/test/output.jl b/test/output.jl new file mode 100644 index 0000000..dcecdf9 --- /dev/null +++ b/test/output.jl @@ -0,0 +1,14 @@ +@testset "Output" begin + mutable struct SimpleOutput{T} <: AbstractSolverOutput{T} + status::Symbol + solution + end + + output = SimpleOutput{Float64}(:unknown, 0.0) + + @testset "Show" begin + io = IOBuffer() + print(io, output) + @test String(take!(io)) == "Solver output of type SimpleOutput{Float64}\nStatus: unknown\n" + end +end diff --git a/test/root-finding.jl b/test/root-finding.jl new file mode 100644 index 0000000..e5528cd --- /dev/null +++ b/test/root-finding.jl @@ -0,0 +1,157 @@ +@testset "Root-finding full example" begin + # Problem + """ + RootFindingProblem + + Struct for problem ``f(x) = 0``. + """ + mutable struct RootFindingProblem{T} + f + x₀::T + evals::Int + end + RootFindingProblem(f, x₀::T) where {T} = RootFindingProblem{T}(f, x₀, 0) + function (rfp::RootFindingProblem)(x) + rfp.evals += 1 + rfp.f(x) + end + function SolverCore.reset!(rfp::RootFindingProblem{T}) where {T} + rfp.evals = 0 + end + + # Output + mutable struct RFPOutput{T} <: AbstractSolverOutput{T} + status::Symbol + solution::T + fx::T + evals::Int + elapsed_time::Float64 + end + function RFPOutput( + status::Symbol, + x::T; + fx::T = T(Inf), + evals::Int = -1, + elapsed_time::Float64 = Inf, + ) where {T} + RFPOutput{T}(status, x, fx, evals, elapsed_time) + end + + # Solver + mutable struct Bissection{T} <: AbstractSolver{T} + initialized::Bool + params::Dict + workspace + end + + function Bissection( + rfp::RootFindingProblem{T}; + θ = one(T) / 2, + δ = one(T), + explorer = true, + explorer_tries = 3, + ) where {T} + Bissection{T}(true, Dict(:θ => θ, :δ => δ, :explorer => true, :explorer_tries => 3), []) + end + + SolverCore.parameters(::Type{Bissection{T}}) where {T} = ( + θ = (default = one(T) / 2, type = T, scale = :linear, min = T(0.1), max = T(0.9)), + δ = (default = one(T), type = T, scale = :log, min = √eps(T), max = T(10)), + explorer = (default = true, type = Bool), + explorer_tries = (default = 3, type = Int, min = 1, max = 3), + ) + function SolverCore.are_valid_parameters(::Type{Bissection{T}}, θ, δ, _, _2) where {T} + return (0 ≤ θ ≤ 1) && δ > 0 + end + + function SolverCore.solve!( + solver::Bissection, + f::RootFindingProblem; + atol = 1e-3, + rtol = 1e-3, + kwargs..., + ) + for (k, v) in kwargs + solver.params[k] = v + end + + θ = solver.params[:θ] + δ = solver.params[:δ] + explorer = solver.params[:explorer] + explorer_tries = solver.params[:explorer_tries] + a, b = f.x₀ - δ, f.x₀ + δ + t₀ = time() + + fa, fb = f(a), f(b) + ϵ = atol + rtol * (abs(fa) + abs(fb)) / 2 + solved = false + if abs(fa) < ϵ + return RFPOutput(:acceptable, a, evals = f.evals, fx = fa, elapsed_time = time() - t₀) + elseif abs(fb) < ϵ + return RFPOutput(:acceptable, b, evals = f.evals, fx = fb, elapsed_time = time() - t₀) + elseif fa * fb > 0 + error("Increasing coverage") + end + x = a * θ + b * (1 - θ) + fx = f(x) + while abs(fx) ≥ ϵ && f.evals < 100 + (a, b, fa, fb) = fa * fx < 0 ? (a, x, fa, fx) : (x, b, fx, fb) + x = a * θ + b * (1 - θ) + fx = f(x) + if explorer + for k = 1:explorer_tries + r = rand() + xt = a * r + b * (1 - r) + ft = f(xt) + if abs(ft) < abs(fx) + x, fx = xt, ft + end + end + end + end + status = :unknown + if abs(fx) < ϵ + status = :acceptable + elseif f.evals ≥ 100 + status = :max_eval + end + return RFPOutput(status, x, evals = f.evals, fx = fx, elapsed_time = time() - t₀) + end + + # Testing + rfp_problems = [ + RootFindingProblem(x -> x - π, 3.0), + RootFindingProblem(x -> x^3 - 2, 1.0), + RootFindingProblem(x -> x * exp(x) - 1, 1.0), + RootFindingProblem(x -> x / (1 + x^2) - 0.5, 0.0), + RootFindingProblem(x -> 1 / (1 + exp(-x)) - 0.5, 1.0), + ] + + for f in rfp_problems + solver = Bissection(f) + output = solve!(solver, f) + @test abs(output.fx) < 1e-2 + + solver = Bissection(f, θ = 0.1) + output = solve!(solver, f) + @test abs(output.fx) < 1e-2 + end + + # Tuning + Random.seed!(0) + grid = with_logger(NullLogger()) do + grid_search_tune( + Bissection, + rfp_problems, + success = o -> o.status == :acceptable, + costs = [(o -> o.elapsed_time, 100.0), (o -> o.evals, 100)], + grid_length = 9, + δ = 10.0 .^ (-5:1), + ) + end + best = sort(grid[:], by = x -> x[2][2])[1][1] + @test best[1] ≈ 0.3 + @test best[2] ≈ 1 + @test !best[3] + @test best[4] == 1 +end diff --git a/test/runtests.jl b/test/runtests.jl index 4af266b..a796e32 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,13 +1,11 @@ # This package using SolverCore -# Auxiliary packages -using ADNLPModels, NLPModels - # stdlib -using LinearAlgebra, Logging, Test +using Logging, Printf, Random, Test -include("dummy_solver.jl") +include("solver.jl") +include("output.jl") -include("test_logging.jl") -include("test_stats.jl") +include("logger.jl") +include("root-finding.jl") diff --git a/test/solver.jl b/test/solver.jl new file mode 100644 index 0000000..e78e30e --- /dev/null +++ b/test/solver.jl @@ -0,0 +1,37 @@ +@testset "Solver" begin + mutable struct NoSolver{T} <: AbstractSolver{T} end + solver = NoSolver{Float64}() + + @testset "Show" begin + io = IOBuffer() + print(io, solver) + @test String(take!(io)) == "Solver NoSolver{Float64}\n" + end + + @testset "solve! not implemented" begin + @test_throws MethodError solve!(solver, 0) + end + + @testset "Parameters" begin + SolverCore.parameters(::Type{NoSolver{T}}) where {T} = + (Ω = (default = zero(T), type = T, scale = :real, min = -one(T), max = one(T))) + P = parameters(NoSolver{Float64}) + @test P == parameters(NoSolver) + @test P == parameters(solver) + + SolverCore.are_valid_parameters(::Type{NoSolver{T}}, Ω) where {T} = (-1 ≤ Ω ≤ 1) + @test are_valid_parameters(NoSolver, 1) + @test are_valid_parameters(solver, 1) + @test are_valid_parameters(solver, 0) + @test are_valid_parameters(solver, -1) + @test !are_valid_parameters(solver, -2) + end + + @testset "Traits" begin + @test problem_types_handled(NoSolver) == [] + SolverCore.problem_types_handled(::Type{NoSolver}) = [:none] + @test problem_types_handled(NoSolver) == [:none] + @test can_solve_type(NoSolver, :none) + @test !can_solve_type(NoSolver, :other) + end +end diff --git a/test/test_logging.jl b/test/test_logging.jl deleted file mode 100644 index 7c46060..0000000 --- a/test/test_logging.jl +++ /dev/null @@ -1,17 +0,0 @@ -function test_logging() - nlps = [ADNLPModel(x -> sum(x.^k), ones(2k), name="Sum of power $k") for k = 2:4] - push!(nlps, ADNLPModel(x -> dot(x, x), ones(2), x->[sum(x)-1], [0.0], [0.0], name="linquad")) - - @info "Testing logger" - log_header([:col_float, :col_int, :col_symbol, :col_string], [Float64, Int, Symbol, String]) - log_row([1.0, 1, :one, "one"]) - log_row([Float64, Int, Symbol, String]) - - with_logger(ConsoleLogger()) do - @info "Testing dummy solver with logger" - dummy_solver(nlps[1], max_eval=20) - reset!.(nlps) - end -end - -test_logging() diff --git a/test/test_stats.jl b/test/test_stats.jl deleted file mode 100644 index f0a0332..0000000 --- a/test/test_stats.jl +++ /dev/null @@ -1,96 +0,0 @@ -function test_stats() - show_statuses() - nlp = ADNLPModel(x->dot(x,x), zeros(2)) - stats = GenericExecutionStats( - :first_order, - nlp, - objective=1.0, - dual_feas=1e-12, - solution=ones(100), - iter=10, - solver_specific=Dict(:matvec=>10, - :dot=>25, - :empty_vec=>[], - :small_vec=>[2.0;3.0], - :axpy=>20, - :ray=>-1 ./ (1:100) - ) - ) - - show(stats) - print(stats) - println(stats) - open("teststats.out", "w") do f - println(f, stats) - end - - println(stats, showvec=(io,x)->print(io,x)) - open("teststats.out", "a") do f - println(f, stats, showvec=(io,x)->print(io,x)) - end - - line = [:status, :neval_obj, :objective, :iter] - for field in line - value = statsgetfield(stats, field) - println("$field -> $value") - end - println(statshead(line)) - println(statsline(stats, line)) - - @testset "Testing inference" begin - for T in (Float16, Float32, Float64, BigFloat) - nlp = ADNLPModel(x->dot(x, x), ones(T, 2)) - - stats = GenericExecutionStats(:first_order, nlp) - @test stats.status == :first_order - @test typeof(stats.objective) == T - @test typeof(stats.dual_feas) == T - @test typeof(stats.primal_feas) == T - - nlp = ADNLPModel(x->dot(x, x), ones(T, 2), x->[sum(x)-1], [0.0], [0.0]) - - stats = GenericExecutionStats(:first_order, nlp) - @test stats.status == :first_order - @test typeof(stats.objective) == T - @test typeof(stats.dual_feas) == T - @test typeof(stats.primal_feas) == T - end - end - - @testset "Test throws" begin - @test_throws Exception GenericExecutionStats(:bad, nlp) - @test_throws Exception GenericExecutionStats(:unkwown, nlp, bad=true) - end - - @testset "Testing Dummy Solver with multi-precision" begin - for T in (Float16, Float32, Float64, BigFloat) - nlp = ADNLPModel(x->dot(x, x), ones(T, 2)) - - with_logger(NullLogger()) do - stats = dummy_solver(nlp) - end - @test typeof(stats.objective) == T - @test typeof(stats.dual_feas) == T - @test typeof(stats.primal_feas) == T - @test eltype(stats.solution) == T - @test eltype(stats.multipliers) == T - @test eltype(stats.multipliers_L) == T - @test eltype(stats.multipliers_U) == T - - nlp = ADNLPModel(x->dot(x, x), ones(T, 2), x->[sum(x)-1], [0.0], [0.0]) - - with_logger(NullLogger()) do - stats = dummy_solver(nlp) - end - @test typeof(stats.objective) == T - @test typeof(stats.dual_feas) == T - @test typeof(stats.primal_feas) == T - @test eltype(stats.solution) == T - @test eltype(stats.multipliers) == T - @test eltype(stats.multipliers_L) == T - @test eltype(stats.multipliers_U) == T - end - end -end - -test_stats() From a11f85da8908ee066e6e824b0c198d6ee9964af3 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 16 Apr 2021 00:50:51 -0300 Subject: [PATCH 04/10] Solver revamp docs folder and formatter --- .JuliaFormatter.toml | 7 +++++++ docs/make.jl | 12 ++++++------ docs/src/api.md | 18 ------------------ docs/src/index.md | 8 +++++++- docs/src/reference.md | 13 +++++++++++++ 5 files changed, 33 insertions(+), 25 deletions(-) create mode 100644 .JuliaFormatter.toml delete mode 100644 docs/src/api.md diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..15fa654 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,7 @@ +margin = 100 +indent = 2 +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +annotate_untyped_fields_with_any = false +normalize_line_endings = "unix" \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index d9dd66c..8ba3601 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,16 +5,16 @@ makedocs( doctest = true, linkcheck = true, strict = true, - format = Documenter.HTML(assets = ["assets/style.css"], prettyurls = get(ENV, "CI", nothing) == "true"), + format = Documenter.HTML( + assets = ["assets/style.css"], + prettyurls = get(ENV, "CI", nothing) == "true", + ), sitename = "SolverCore.jl", - pages = ["Home" => "index.md", - "API" => "api.md", - "Reference" => "reference.md", - ] + pages = ["Home" => "index.md", "Reference" => "reference.md"], ) deploydocs( repo = "github.com/JuliaSmoothOptimizers/SolverCore.jl.git", push_preview = true, - devbranch = "master" + devbranch = "master", ) diff --git a/docs/src/api.md b/docs/src/api.md deleted file mode 100644 index f61d1a6..0000000 --- a/docs/src/api.md +++ /dev/null @@ -1,18 +0,0 @@ -# API - -```@contents -Pages = ["api.md"] -``` -## Logging - -```@docs -log_header -log_row -``` - -## Stats - -```@docs -GenericExecutionStats -show_statuses -``` diff --git a/docs/src/index.md b/docs/src/index.md index 3f547d2..1ba45be 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,3 +1,9 @@ # [SolverCore.jl documentation](@id Home) -Core package to build novel optimization algorithms in Julia. +Core package to build JSO-compliant solvers in Julia. + +--- + +This package is extended for specific problem types: +- **OptSolver.jl**: For optimization problems +- **LinearSolvers.jl**: For linear solvers. \ No newline at end of file diff --git a/docs/src/reference.md b/docs/src/reference.md index 2dec9b1..d35afcb 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -1,4 +1,17 @@ # Reference +## Contents + +```@contents +Pages = ["reference.md"] +``` + +## Index + ```@index +Pages = ["reference.md"] ``` + +```@autodocs +Modules = [SolverCore] +``` \ No newline at end of file From e0857c87e8518b37ad0e07251760d5a5a23744c4 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Fri, 16 Apr 2021 15:22:20 -0300 Subject: [PATCH 05/10] Add container type S --- src/output.jl | 6 ++++-- src/solver.jl | 7 +++++-- test/output.jl | 7 ++++--- test/root-finding.jl | 4 ++-- test/solver.jl | 12 ++++++------ 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/output.jl b/src/output.jl index 1251a73..bc5714c 100644 --- a/src/output.jl +++ b/src/output.jl @@ -2,14 +2,16 @@ export AbstractSolverOutput # TODO: Define the required fields and API for all Outputs """ - AbstractSolverOutput{T} + AbstractSolverOutput{T,S} Base type for output of JSO-compliant solvers. An output must have at least the following: - `status :: Symbol` - `solution` + +The type `T` is used for element types of the arrays, and `S` is used for the storage container type. """ -abstract type AbstractSolverOutput{T} end +abstract type AbstractSolverOutput{T, S} end # TODO: Decision: Should STATUSES be fixed? Should it be all here? const STATUSES = Dict( diff --git a/src/solver.jl b/src/solver.jl index 1921294..64d917b 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -2,15 +2,18 @@ export AbstractSolver, solve! # TODO: Define the required fields and API for all Solvers """ - AbstractSolver + AbstractSolver{T,S} Base type for JSO-compliant solvers. + A solver must have three members: - `initialized :: Bool`, indicating whether the solver was initialized - `params :: Dict`, a dictionary of parameters for the solver - `workspace`, a named tuple with arrays used by the solver. + +The type `T` is used for element types of the arrays, and `S` is used for the storage container type. """ -abstract type AbstractSolver{T} end +abstract type AbstractSolver{T, S} end function Base.show(io::IO, solver::AbstractSolver) println(io, "Solver $(typeof(solver))") diff --git a/test/output.jl b/test/output.jl index dcecdf9..bbe4482 100644 --- a/test/output.jl +++ b/test/output.jl @@ -1,14 +1,15 @@ @testset "Output" begin - mutable struct SimpleOutput{T} <: AbstractSolverOutput{T} + mutable struct SimpleOutput{T, S} <: AbstractSolverOutput{T, S} status::Symbol solution end - output = SimpleOutput{Float64}(:unknown, 0.0) + output = SimpleOutput{Float64, Vector{Float64}}(:unknown, 0.0) @testset "Show" begin io = IOBuffer() print(io, output) - @test String(take!(io)) == "Solver output of type SimpleOutput{Float64}\nStatus: unknown\n" + @test String(take!(io)) == + "Solver output of type SimpleOutput{Float64, Vector{Float64}}\nStatus: unknown\n" end end diff --git a/test/root-finding.jl b/test/root-finding.jl index e5528cd..b5c284d 100644 --- a/test/root-finding.jl +++ b/test/root-finding.jl @@ -20,7 +20,7 @@ end # Output - mutable struct RFPOutput{T} <: AbstractSolverOutput{T} + mutable struct RFPOutput{T} <: AbstractSolverOutput{T, T} status::Symbol solution::T fx::T @@ -38,7 +38,7 @@ end # Solver - mutable struct Bissection{T} <: AbstractSolver{T} + mutable struct Bissection{T} <: AbstractSolver{T, T} initialized::Bool params::Dict workspace diff --git a/test/solver.jl b/test/solver.jl index e78e30e..9b12cdf 100644 --- a/test/solver.jl +++ b/test/solver.jl @@ -1,11 +1,11 @@ @testset "Solver" begin - mutable struct NoSolver{T} <: AbstractSolver{T} end - solver = NoSolver{Float64}() + mutable struct NoSolver{T, S} <: AbstractSolver{T, S} end + solver = NoSolver{Float64, Vector{Float64}}() @testset "Show" begin io = IOBuffer() print(io, solver) - @test String(take!(io)) == "Solver NoSolver{Float64}\n" + @test String(take!(io)) == "Solver NoSolver{Float64, Vector{Float64}}\n" end @testset "solve! not implemented" begin @@ -13,13 +13,13 @@ end @testset "Parameters" begin - SolverCore.parameters(::Type{NoSolver{T}}) where {T} = + SolverCore.parameters(::Type{NoSolver{T, S}}) where {T, S} = (Ω = (default = zero(T), type = T, scale = :real, min = -one(T), max = one(T))) - P = parameters(NoSolver{Float64}) + P = parameters(NoSolver{Float64, Vector{Float64}}) @test P == parameters(NoSolver) @test P == parameters(solver) - SolverCore.are_valid_parameters(::Type{NoSolver{T}}, Ω) where {T} = (-1 ≤ Ω ≤ 1) + SolverCore.are_valid_parameters(::Type{NoSolver{T, S}}, Ω) where {T, S} = (-1 ≤ Ω ≤ 1) @test are_valid_parameters(NoSolver, 1) @test are_valid_parameters(solver, 1) @test are_valid_parameters(solver, 0) From 7e0f05230149d783673aaa699bf6f8f50f04d339 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Sat, 17 Apr 2021 18:24:50 -0300 Subject: [PATCH 06/10] Change version to 0.2.0-dev --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 078bd2e..7b7b919 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "SolverCore" uuid = "ff4d7338-4cf1-434d-91df-b86cb86fb843" -version = "0.1.0" +version = "0.2.0-dev" [deps] Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" From 27b3674bf7c0bb5ac2330f5f31daf87f94287814 Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Sat, 17 Apr 2021 21:36:56 -0300 Subject: [PATCH 07/10] Change reset! to reset_problem! --- src/grid-search-tuning.jl | 7 +++---- test/root-finding.jl | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/grid-search-tuning.jl b/src/grid-search-tuning.jl index 5dd5e41..b9b10a5 100644 --- a/src/grid-search-tuning.jl +++ b/src/grid-search-tuning.jl @@ -1,7 +1,6 @@ -export grid_search_tune +export grid_search_tune, reset_problem! -# TODO: Issue: For grid_search_tune to work, we need to define `reset!`, but LinearOperators also define reset! -function reset! end +function reset_problem! end # TODO: Decide success and costs of grid_search_tune below @@ -70,7 +69,7 @@ function grid_search_tune( cost(θ) = begin total_cost = [zero(x[2]) for x in costs] for problem in problems - reset!(problem) + reset_problem!(problem) try solver = Solver(problem) P = (k => θi for (k, θi) in zip(keys(solver_params), θ)) diff --git a/test/root-finding.jl b/test/root-finding.jl index b5c284d..4c89333 100644 --- a/test/root-finding.jl +++ b/test/root-finding.jl @@ -15,7 +15,7 @@ rfp.evals += 1 rfp.f(x) end - function SolverCore.reset!(rfp::RootFindingProblem{T}) where {T} + function SolverCore.reset_problem!(rfp::RootFindingProblem{T}) where {T} rfp.evals = 0 end From 8c5af78f4c04561808d16b7b1c26d078daeb32ae Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Sun, 18 Apr 2021 13:12:48 -0300 Subject: [PATCH 08/10] Add more types to grid search --- src/grid-search-tuning.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/grid-search-tuning.jl b/src/grid-search-tuning.jl index b9b10a5..caf38f8 100644 --- a/src/grid-search-tuning.jl +++ b/src/grid-search-tuning.jl @@ -49,6 +49,10 @@ function grid_search_tune( params[k] = (false, true) elseif v[:type] <: Integer params[k] = v[:min]:v[:max] + elseif v[:type] == Symbol || v[:type] <: AbstractString + params[k] = v[:options] + else + error(ArgumentError("Unexpected parameter type for $k: type = $(v[:type])")) end end for (k, v) in kwargs From 7f63937ab341c3aa41c87153fc7b820f1525a2ae Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Wed, 21 Apr 2021 15:57:50 -0300 Subject: [PATCH 09/10] Add functions useful for SolverBenchmark --- src/SolverCore.jl | 1 + src/grid-search-tuning.jl | 2 -- src/output.jl | 5 ++++- src/problem.jl | 2 ++ test/root-finding.jl | 13 ++++++++++++- test/solver.jl | 4 +++- 6 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 src/problem.jl diff --git a/src/SolverCore.jl b/src/SolverCore.jl index 49bc3aa..fa54a96 100644 --- a/src/SolverCore.jl +++ b/src/SolverCore.jl @@ -4,6 +4,7 @@ module SolverCore using Logging, Printf using OrderedCollections +include("problem.jl") include("solver.jl") include("output.jl") diff --git a/src/grid-search-tuning.jl b/src/grid-search-tuning.jl index caf38f8..6158790 100644 --- a/src/grid-search-tuning.jl +++ b/src/grid-search-tuning.jl @@ -1,7 +1,5 @@ export grid_search_tune, reset_problem! -function reset_problem! end - # TODO: Decide success and costs of grid_search_tune below """ diff --git a/src/output.jl b/src/output.jl index bc5714c..1d92b85 100644 --- a/src/output.jl +++ b/src/output.jl @@ -1,4 +1,7 @@ -export AbstractSolverOutput +export AbstractSolverOutput, solver_output_type + +solver_output_type(::Type{S}) where S <: AbstractSolver = error("Output type not defined for $S") +solver_output_type(::S) where S <: AbstractSolver = solver_output_type(S) # TODO: Define the required fields and API for all Outputs """ diff --git a/src/problem.jl b/src/problem.jl new file mode 100644 index 0000000..cd49188 --- /dev/null +++ b/src/problem.jl @@ -0,0 +1,2 @@ +function reset_problem! end +function problem_name end \ No newline at end of file diff --git a/test/root-finding.jl b/test/root-finding.jl index 4c89333..9bed987 100644 --- a/test/root-finding.jl +++ b/test/root-finding.jl @@ -44,7 +44,9 @@ workspace end - function Bissection( + SolverCore.solver_output_type(::Type{Bissection{T}}) where T = RFPOutput{T} + + function Bissection{T}( rfp::RootFindingProblem{T}; θ = one(T) / 2, δ = one(T), @@ -54,6 +56,8 @@ Bissection{T}(true, Dict(:θ => θ, :δ => δ, :explorer => true, :explorer_tries => 3), []) end + Bissection(rfp::RootFindingProblem{T}; kwargs...) where {T} = Bissection{T}(rfp; kwargs...) + SolverCore.parameters(::Type{Bissection{T}}) where {T} = ( θ = (default = one(T) / 2, type = T, scale = :linear, min = T(0.1), max = T(0.9)), δ = (default = one(T), type = T, scale = :log, min = √eps(T), max = T(10)), @@ -137,6 +141,13 @@ @test abs(output.fx) < 1e-2 end + for T in [Float16, Float32, Float64, BigFloat] + @test solver_output_type(Bissection{T}) == RFPOutput{T} + f = RootFindingProblem(x -> x, one(T)) + solver = Bissection(f) + @test solver_output_type(solver) == RFPOutput{T} + end + # Tuning Random.seed!(0) grid = with_logger(NullLogger()) do diff --git a/test/solver.jl b/test/solver.jl index 9b12cdf..5e7740a 100644 --- a/test/solver.jl +++ b/test/solver.jl @@ -8,8 +8,10 @@ @test String(take!(io)) == "Solver NoSolver{Float64, Vector{Float64}}\n" end - @testset "solve! not implemented" begin + @testset "Not implemented" begin @test_throws MethodError solve!(solver, 0) + @test_throws ErrorException solver_output_type(solver) + @test_throws ErrorException solver_output_type(NoSolver) end @testset "Parameters" begin From 9ceb000ea4e63fec53d7e432be69b2fce35e15ad Mon Sep 17 00:00:00 2001 From: Abel Soares Siqueira Date: Thu, 22 Apr 2021 15:40:39 -0300 Subject: [PATCH 10/10] More functions for SolverBenchmark --- src/problem.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/problem.jl b/src/problem.jl index cd49188..2243fbe 100644 --- a/src/problem.jl +++ b/src/problem.jl @@ -1,2 +1,4 @@ function reset_problem! end -function problem_name end \ No newline at end of file +function problem_name end +function problem_info end +function output_info end \ No newline at end of file