Skip to content

Commit

Permalink
Merge pull request #69 from jmejia8/develop
Browse files Browse the repository at this point in the history
Let the user set initial solutions
  • Loading branch information
jmejia8 authored Mar 8, 2023
2 parents 32ac5c3 + 48a7eab commit 01296d6
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 73 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Metaheuristics"
uuid = "bcdb8e00-2c21-11e9-3065-2b553b22f898"
authors = ["Jesus Mejia <[email protected]>"]
version = "3.2.14"
version = "3.2.15"

[deps]
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
Expand Down
4 changes: 4 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ Metaheuristics.compare
Metaheuristics.gen_initial_state
```

```@docs
set_user_solutions!
```

## Variation

```@docs
Expand Down
2 changes: 1 addition & 1 deletion src/DecisionMaking/ROI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function roiarchiving(population, w, parameters::ROIArchiving; verbose=true)

δ_w = parameters.δ_w_transformed

size(w,2) != length(δ_w) && error("|weight_points| is different to |δ_w|")
size(w,1) != length(δ_w) && error("|weight_points| is different to |δ_w|")

non_dominated = Metaheuristics.get_non_dominated_solutions_perm(population)
isempty(non_dominated) && return Int[]
Expand Down
3 changes: 3 additions & 0 deletions src/Metaheuristics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export GenerationalReplacement, ElitistReplacement
export sample, LatinHypercubeSampling, Grid
export εDE, Restart
export optimize!
export set_user_solutions!

include("externals.jl")

Expand All @@ -51,6 +52,8 @@ include("common/repair.jl")
include("common/stop.jl")
include("common/compare.jl")
include("common/non-dominated-sorting.jl")
include("common/set_user_solutions.jl")



include("TestProblems/TestProblems.jl")
Expand Down
5 changes: 4 additions & 1 deletion src/algorithms/ABC/ABC.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ function initialize!(
options.debug && @info "Increasing f calls limit to $(options.f_calls_limit)"
end

bees = initialbees(parameters.N, problem)

_st = gen_initial_state(problem,parameters,information,options,status)
bees = [Bee(sol) for sol in _st.population]

nevals = length(bees)

best_sol = deepcopy(getBestBee(bees))
Expand Down
3 changes: 2 additions & 1 deletion src/algorithms/ABC/bee_dynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ function chooseBest(bees, best)
return best
end

#=
function initialbees(N, problem)
P = generate_population(N, problem)
return [ Bee(sol) for sol in P ]
end

=#
15 changes: 4 additions & 11 deletions src/algorithms/CGSA/CGSA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,26 +135,19 @@ function initialize!(

max_it = 500
options.iterations = options.iterations == 0 ? max_it : options.iterations
options.f_calls_limit = options.f_calls_limit == 0 ? options.iterations * N : options.f_calls_limit

# random initialization for agents.
P = generate_population(N, problem)

# Current best
theBest = get_best(P)


status = State(theBest, P)
status.f_calls = N
status = gen_initial_state(problem,parameters,information,options,status)
N = parameters.N
options.f_calls_limit = options.f_calls_limit == 0 ? options.iterations * N : options.f_calls_limit

# Velocity
parameters.V = isempty(parameters.V) ? zeros(N,D) : parameters.V
# Postions
parameters.X = isempty(parameters.X) ? positions(status) : parameters.X
# function values
if isempty(parameters.fitness)

parameters.fitness = fval.(status.population)
parameters.fitness = fvals(status.population)
end


Expand Down
7 changes: 2 additions & 5 deletions src/algorithms/MCCGA/MCCGA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,8 @@ function initialize!(

parameters.probvector = initialprobs(lower, upper, maxsamples = parameters.maxsamples)

# sample a vector to create an initial State
x = sample(parameters.probvector) |> floats
initial_sol = create_solution(x, problem)
return State(initial_sol, [initial_sol for i in 1:parameters.N])

# sample vectors to create an initial State
return gen_initial_state(problem,parameters,information,options,status)
end

function update_state!(
Expand Down
10 changes: 4 additions & 6 deletions src/algorithms/WOA/WOA.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,14 @@ function initialize!(
options.iterations * parameters.N : options.f_calls_limit


P = generate_population(parameters.N, problem)
best_sol = deepcopy(get_best(P))
status = State(best_sol, P)
# P = generate_population(parameters.N, problem)
# best_sol = deepcopy(get_best(P))
# status = State(best_sol, P)
#=
status.population = P
status.best_sol = deepcopy(get_best(status.population))
=#
status.f_calls = parameters.N

status
return gen_initial_state(problem,parameters,information,options,status)

end

Expand Down
42 changes: 0 additions & 42 deletions src/algorithms/algorithm.jl
Original file line number Diff line number Diff line change
@@ -1,42 +0,0 @@
"""
gen_initial_state(problem,parameters,information,options)
Generate an initial state, i.e., compute uniformly distributed random vectors in bounds,
after that are evaluated in objective function. This method require that `parameters.N`
is valid attribute.
"""
gen_initial_state(problem,parameters,information,options,status::State{Any}) = gen_initial_state(problem,parameters,information,options)


function gen_initial_state(problem,parameters,information,options, status)
parameters.N != length(status.population) &&
error("Population size in provided State differs from that in parameters")


size(problem.bounds,2) != length(get_position(status.best_sol)) &&
error("Invalid population (dimension does not match with bounds)")

return State(status.best_sol, status.population)


end

function gen_initial_state(problem,parameters,information,options)
# population array
population = generate_population(parameters.N, problem,ε=options.h_tol)

# best solution
best_solution = get_best(population)

return State(best_solution, population; f_calls = length(population), iteration=1)
end

function Base.show(io::IO, parameters::AbstractParameters)
s = typeof(parameters)

vals = string.(map(f -> getfield(parameters, f), fieldnames(s)))
str = string(s) * "(" * join(string.(fieldnames(s)) .* "=" .* vals, ", ") * ")"

print(io, str)
end

67 changes: 67 additions & 0 deletions src/common/gen_initial_state.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
gen_initial_state(problem,parameters,information,options)
Generate an initial state, i.e., compute uniformly distributed random vectors in bounds,
after that are evaluated in objective function. This method require that `parameters.N`
is valid attribute.
"""
gen_initial_state(problem,parameters,information,options,status::State{Any}) = gen_initial_state(problem,parameters,information,options)


function gen_initial_state(problem,parameters,information,options, status)
if parameters.N != length(status.population)
options.debug && @warn("Population size in provided State differs from that in parameters")
end

size(problem.bounds,2) != length(get_position(status.best_sol)) &&
error("Invalid population (dimension does not match with bounds)")

_complete_population!(status,problem,parameters,information,options)

best_solution = get_best(status.population)
# check if a better solution was found
if is_better(status.best_sol, best_solution)
best_solution = status.best_sol
end

return State(best_solution, status.population;f_calls = length(status.population))
end

function gen_initial_state(problem,parameters,information,options)
# population array
population = generate_population(parameters.N, problem,ε=options.h_tol)

# best solution
best_solution = get_best(population)

return State(best_solution, population; f_calls = length(population), iteration=1)
end

function Base.show(io::IO, parameters::AbstractParameters)
s = typeof(parameters)

vals = string.(map(f -> getfield(parameters, f), fieldnames(s)))
str = string(s) * "(" * join(string.(fieldnames(s)) .* "=" .* vals, ", ") * ")"

print(io, str)
end

function _complete_population!(status,problem,parameters,information,options)
if parameters.N < length(status.population)
# increase population if necessary
parameters.N = length(status.population)
# TODO: use this options.debug == true to show the message?
@warn("Population size increased to $(parameters.N) due to initial solutions.")
return
end

if parameters.N > length(status.population)
# complete population with random in bounds
n = parameters.N - length(status.population)
missing_sols = generate_population(n, problem,ε=options.h_tol)
# insert new solution into population
append!(status.population, missing_sols)
end

end

86 changes: 86 additions & 0 deletions src/common/set_user_solutions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
include("gen_initial_state.jl")

"""
set_user_solutions!(optimizer, x, fx)
Provide initial solutions to the `optimizer`.
- `x` can be a `Vector` and `fx` a function or `fx = f(x)`
- `x` can be a matrix containing solutions in rows.
### Example
```julia-repl
julia> f(x) = abs(x[1]) + x[2] + x[3]^2 # objective function
f (generic function with 1 method)
julia> algo = ECA(N = 61); # optimizer
julia> # one solution can be provided
x0 = [0.5, 0.5, 0.5];
julia> set_user_solutions!(algo, x0, f);
julia> # providing multiple solutions
X0 = rand(30, 3); # 30 solutions with dim 3
julia> set_user_solutions!(algo, X0, f);
julia> optimize(f, [0 0 0; 1 1 1.0], algo)
+=========== RESULT ==========+
iteration: 413
minimum: 0
minimizer: [0.0, 0.0, 0.0]
f calls: 25132
total time: 0.0856 s
stop reason: Small difference of objective function values.
+============================+
```
"""
function set_user_solutions!(algo::AbstractAlgorithm, solution::AbstractSolution)
status = algo.status
if !isnothing(status.best_sol) && !isempty(status.population)
push!(status.population, solution)
else
algo.status = State(solution, [solution])
end
algo
end


function set_user_solutions!(algo::AbstractAlgorithm, x::AbstractVector, fx)
set_user_solutions!(algo, create_child(x, fx))
end

function set_user_solutions!(algo::AbstractAlgorithm, x::AbstractVector, f::Function)
set_user_solutions!(algo, x, f(x))
end


function set_user_solutions!(algo::AbstractAlgorithm, X::AbstractMatrix, fX::AbstractVector)
n = size(X, 1)
m = length(fX)

if n != m
@warn "$(n) decision vectors provided but $(m) objective values."
n = min(m, n)
println("Taking ", n, " as the number of initial solutions.")
end

# nothing to do due to it is necessary the objective value
n == 0 && (return algo)

# TODO: this part can be parallelized
for i in 1:n
set_user_solutions!(algo, X[i,:], fX[i])
end

# TODO check population size provided in algo.parameters.N

algo
end

function set_user_solutions!(algo::AbstractAlgorithm, X::AbstractMatrix, f::Function)
set_user_solutions!(algo, X, [f(X[i,:]) for i in 1:size(X,1)])
end

8 changes: 4 additions & 4 deletions test/decisionmaking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@
end

@testset "DecisionMaking: ROI" begin
function test_roi()
function test_roi(w = [0.1 0.9; 0.9 0.1], δ = [0.1,0.1])
_, _, pf = Metaheuristics.TestProblems.ZDT1();
res = State(pf[1], pf)

w = [0.1 0.9; 0.9 0.1]
δ = [0.1,0.1]

method = ROIArchiving(δ)
idx = decisionmaking(res, w, method)
@test !isempty(idx)
sols = best_alternative(res, w, method)
@test length(sols) == length(idx)
end


test_roi()
test_roi([0.0 1; 1 0; 0.5 0.5], [0.1, 0.2, 0.3])
end

@testset "DecisionMaking: JMcDM" begin
Expand Down
Loading

0 comments on commit 01296d6

Please sign in to comment.