Development
If you are contributing a new algorithm to this package, you need to know an internal API which allows to add a new algorithm without considerable changes to overall structure of the package.
Adding an algorithm
If you're contributing a new algorithm, you shouldn't need to touch any of the code in src/api/optimize.jl. You should rather add a file named (solver is the name of the solver) algo.jl in src, and make sure that you define an optimizer parameters and state types, initial_population that initializes a population of individual objects, a state type that holds all variables that are (re)used throughout the iterative procedure, an initial_state that initializes such a state, and an update_state! method that does the actual work.
Algorithm
Every optimization algorithm have to implement an algorithm parameters type derived from AbstractOptimizer type, e.g. struct Algo <: AbstractOptimizer end, with appropriate fields, a default constructor with a keyword for each field.
Function initial_state returns an initial state for the algorithm, see State section. Function update_state! returns a Bool value. If the state update is successfully completed then the function returns false, otherwise true.
Evolutionary.AbstractOptimizer — Type
Abstract evolutionary optimizer algorithm
State
Every optimization algorithm must implement a state type derived from AbstractOptimizerState, for example struct AlgoState <: AbstractOptimizerState end. Derived types implement NLSolversBase.value and minimizer.
Evolutionary.AbstractOptimizerState — Type
Abstract type for defining an optimizer state
Every algorithm have to implement a state type derived from this abstract type.
NLSolversBase.value — Method
value(state)Returns a minimum value of the current state.
Evolutionary.minimizer — Method
minimizer(state)Returns a minimizer object in the current state.
Evolutionary.terminate — Method
terminate(state)Returns true if the state requires early termination.
Objective API
Evolutionary extends the following NLSolversBase functions for use with EvolutionaryObjective values when implementing an optimizer state or evaluation strategy. Import them from NLSolversBase rather than from Evolutionary.
NLSolversBase.value — Function
value(state)Returns a minimum value of the current state.
value(obj::EvolutionaryObjective)
value(obj::EvolutionaryObjective, x)Return the cached objective value of obj, or evaluate the objective at x. Evaluation increments f_calls.
Arguments
obj: Objective wrapper to inspect or evaluate.x: Candidate point supplied to the wrapped objective.
Examples
julia> obj = EvolutionaryObjective(sum, zeros(2));
julia> value(obj, [1.0, 2.0])
3.0NLSolversBase.value! — Function
value!(obj::EvolutionaryObjective, x)
value!(obj::EvolutionaryObjective, values, population)Evaluate an EvolutionaryObjective, store the result in its cache, and return the stored value. With values and population, evaluate each population member into the supplied output array.
Arguments
obj: Objective wrapper to evaluate.x: Candidate point to evaluate.values: Preallocated output array for population evaluation.population: Candidate points to evaluate.
Examples
julia> obj = EvolutionaryObjective(sum, zeros(2));
julia> value!(obj, [1.0, 2.0])
3.0NLSolversBase.value!! — Function
value!!(obj::EvolutionaryObjective, x)
value!!(obj::EvolutionaryObjective, values, x)Evaluate an EvolutionaryObjective after copying x into its cached input, then return the objective value. Use this variant when later optimizer steps need the evaluated candidate retained by the objective wrapper.
Arguments
obj: Objective wrapper to evaluate and update.x: Candidate point to cache and evaluate.values: Workspace used by an in-place objective evaluation.
Examples
julia> obj = EvolutionaryObjective(sum, zeros(2));
julia> value!!(obj, [1.0, 2.0])
3.0NLSolversBase.f_calls — Function
f_calls(obj::EvolutionaryObjective)Return the number of objective evaluations performed by obj.
Arguments
obj: Objective wrapper whose evaluation count is queried.
Examples
julia> obj = EvolutionaryObjective(sum, zeros(2));
julia> value!(obj, [1.0, 2.0]); f_calls(obj)
1f_calls(result)Returns a number of an objective function calls.
Population
The evolutionary algorithms require a collection of individuals, population, which the algorithm constantly modifies. The population collection type must be derived from the AbstractVector type. Function initial_population is used for implementing a strategy of population collection initialization.
The initial_population must accept two parameters:
method, an algorithm object derived fromAbstractOptimizertypeindividual, a description of an individual template used to create the population
Following population initialization strategies are available:
Evolutionary.initial_population — Function
initial_population(method, individual::AbstractVector)Initialize population by replicating the individual vector.
initial_population(method, individuals::AbstractVector{<:AbstractVector})Initialize population from the collection of individuals vectors.
initial_population(method, individual::Function)Initialize population from the individual function which returns an individual object.
initial_population(method, individual::AbstractMatrix)Initialize population by replicating the individual matrix.
initial_population(method, bounds::ConstraintBounds)Initialize a random population within the individual bounds.
initial_population(m::TreeGP, expr::{Expr,Nothing}=nothing)Initialize a random population of expressions derived from expr.
Constraints
All constraints derived from the AbstractConstraints abstract type. For the derived type, the constraints interface functions have to be implemented. If the derived type wraps a ConstraintBounds object, the bounds method should be implemented.
Following auxiliary functions are available for every derived type of AbstractConstraints.
Evolutionary.constraint_values — Function
constraint_values(c::AbstractConstraints, x)Return the nonlinear constraint-function values for candidate x under c.
Interface
Custom AbstractConstraints implementations with nonlinear constraints must extend this function. Return nothing when no nonlinear constraint function is present. Otherwise, return an indexable collection ordered consistently with the constraint bounds returned by bounds: equality values first, then inequality values.
Arguments
c: Constraint object being evaluated.x: Candidate optimization variable.
Returns
nothing for constraints without nonlinear constraint functions, or the values used by isfeasible and penalty to evaluate c.
Examples
julia> constraint_values(NoConstraints(), [1.0, 2.0])
nothingEvolutionary.isfeasible — Method
isfeasible(c::AbstractConstraints, x) -> BoolReturn true if point x is feasible, given the constraints object c.
Package provides following additional constrains implementations.
Evolutionary.NoConstraints — Type
Type for an empty set of constraints
Evolutionary.MixedTypePenaltyConstraints — Type
This type provides an additional type constraints on the variables required for mixed integer optimization problems.
Objective
Internally, the objective function is wrapped into EvolutionaryObjective type object.
Evolutionary.EvolutionaryObjective — Type
Wrapper around an objective function (compatible with NLSolversBase).
Evolutionary.EvolutionaryObjective — Method
EvolutionaryObjective(f, x[, F])Constructor for an objective function object around the function f with initial parameter x, and objective value F.
Evolutionary.EvolutionaryObjective — Method
EvolutionaryObjective(f, x::Expr[, F])Constructor for an objective object for a Julia evaluatable expression.
Evolutionary.ismultiobjective — Function
ismultiobjective(objfun)Return true if the function is multi-objective objective.
Parallelization
For additional modes of parallelization of the objective function evaluation, add overrides of the value! function. By default, the fitness of the population is calculated by the following function:
function value!(obj::EvolutionaryObjective{TC,TF,TX,Val{:serial}}, fitness, population::AbstractVector{IT}) where {IT}
n = length(xs)
for i in 1:n
F[i] = value(obj, xs[i])
end
F
endThe first symbolic value type parameter, :serial, corresponds to the default value of the parallelization of the Options object. Any additional overrides with different value type parameters will be triggered by specifying a corresponded value type symbol in the Options.parallelization field. A multi-threaded override of the above evaluation is provided.
Convergence Metrics
In order to add a new convergence metric, create a new type derived from ConvergenceMetric abstract type. In addition, every convergence metric class should implement following interface:
Evolutionary.ConvergenceMetric — Type
Interface for the convergence metrics
Evolutionary.description — Function
description(metric)Return a string with a description of the metric.
Evolutionary.converged — Function
converged(metric)Return true if the convergence is archived for the metric.
converged(result)Returns true if the optimization successfully converged to a minimum value.
Evolutionary.assess! — Function
assess!(metric, state)Asses the convergence of an algorithm using the metricat the state.
Evolutionary.diff — Function
diff(metric)Return the value difference for the metric.
Evolutionary.tolerance — Function
tolerance(metric)Return a tolerance value for the metric.
Note: If the class contains the field Δ, which holds the current difference between last consecutive convergence function evaluations, then the provided Evolutinary.diff function will work correctly. Similarly, if the class has the field tol, which holds a tolerance value, the Evolutionary.tolerance method will work correctly.