This is a short guide to writing new functions for the MOEADr package. This package provides a component-based framework for developing (and applying) Multiobjective Evolutionary Algorithms based on Decomposition (MOEA/D)^{1}.

The modular implementation provided in this package provides control over the following aspects of the algorithm:

*decomp*, the decomposition strategy*aggfun*, the scalar aggregation function*neighbors*, the neighborhood assignment strategy*variation*, the variation operators used*update*, the population update method*constraint*, the constraint-handling method*scaling*, the strategy used for objective scaling*stopcrit*, the stop criteria used by the algorithm

This document describes how to write functions implementing new variants for any of these modules. A general description of the algorithm and the component-based interpretation behind the MOEADr package is available in our paper^{2}

Functions should be preferably defined in the form

*verb_object*(e.g.,*generate_weights*or*evaluate_population*)Please try to follow the policy

*one function, one file, same name*(very short functions for general use can be exceptions - in this case they should be placed, e.g., in the`utils.R`

file.When passing variables between functions, there are three main rules that should be observed:

Unless absolutely impossible, functions should always receive

**all**variables used in the function body via formal arguments, plus whatever other variables may be used downstream using the`...`

input.If it is

*absolutely necessary*, a function can eventually access variables from parent frames using, for instance,`parent.frame()$variable_name`

, but this is**not encouraged**. It is definitely**not kosher**for functions to directly modify variables in the parent environment by any means except by formal, explicit outputs. Previous (development) versions of the`MOEADr`

package used to allow it, but the resulting confusion (particularly when writing unit tests or debugging) heavily outweighted the benefits.Functions should, with few exceptions, be able to handle any number of “useless” variables that may be passed to them (using the

`...`

formal argument).

Documentation should be complete. Please use

`roxygen2`

-style documentation in all functions. This version of the package uses`roxygen2`

version 6.0.1 (which means some degree of*markdown*support and other conveniences).Also, please make liberal use of in-code comments to clarify any non-trivial operation.

**W**: matrix of weights (*N x m*)**X**: matrix of candidate solutions at a given iteration. Each row is a point in the space of variables. (*N x nv*)**Xt**: matrix of incumbent solutions at a given iteration (*N x nv*)**Y**: matrix of objective function values (corresponding to the rows of**X**). Each row is a point in the space of objectives. (*N x m*)**Yt**: matrix of objective function values (corresponding to the rows of**Xt**) (*N x m*)**V**: List object with information related to the constraint values of points in**X**. This list contains three objects:- matrix
**V$Cmatrix**, containing the raw value of each constraint (including box constraints, if present) on each point;

- matrix
**V$Vmatrix**, containing the respective*violation*value of each constraint on each point;

- vector
**V$v**, containing the total sum of violations for each point.

- matrix
**Vt**: List object equivalent to**V**, but related to the points in**Xt****B**: matrix of neighborhoods (*N x T*)**P**: matrix of selection probabilities (derived from**B**) (*N x N*).**nfe**: counter, number of solutions evaluated**iter**: counter, number of iterations**keep.running**: flag. TRUE if any stop criterion is met**time.start**: starting time (system clock) of the algorithm**iter.times**: vector of times (in seconds) spent on each iteration**ls.args**: list containing information related to local search operators (if present)**normYs**: List object containing matrices of normalized objective values (regulated by the*scaling*strategy), plus information on the estimated ideal and nadir points.**bigZ**: matrix of scalarized values for the neighborhood of each subproblem, plus one row containing the scalarized values of the incumbent solutions of each subproblem.**sel.indx**: matrix of selection ranks (lower = better) for each subproblem, calculated from**bigZ**.

To discover the available decomposition strategies, use `get_decomposition_methods()`

. Decomposition functions are called from within `generate_weights()`

.

- INPUTS:
- the decomposition parameters are defined in the input list
*decomp*(use`?moead`

and`?decomposition_sld`

to get details on the structure of*decomp*). Any other required inputs should be explicitly declared in the function definition.

- the decomposition parameters are defined in the input list
- OUTPUTS:
- the function must output a
*N x m*matrix of weights, with*N*the number of subproblems and*m*the number of objectives.

- the function must output a

- The name of the function (and of the file) must have the format
*decomposition_XYZ*, with*XYZ*being the moniker for the contributed method (which is going to be passed as`decomp$name`

). - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_decomposition_methods()`

won’t be able to correctly list the method).

Check *decomposition_sld.R* for a good example of decomposition routine (e.g., to use as a template).

To discover the available decomposition strategies, use `get_scalarization_methods()`

. Scalarization functions are called from within `scalarize_values()`

.

- INPUTS:
- the scalarization parameters are defined in the input list
*aggfun*(use`?moead`

and`?scalarization_pbi`

to get details on the structure of*aggfun*). Any other required inputs (e.g.,**W**,**Y**, etc.) should be explicitly declared in the function definition.

- the scalarization parameters are defined in the input list
- OUTPUTS:
- the function must output a numeric vector of size
*N*, containing the scalarized values.

- the function must output a numeric vector of size

- The name of the function (and of the file) must have the format
*scalarization_XYZ*, with*XYZ*being the moniker for the contributed method (which is going to be passed as*aggfun$name*). - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_scalarization_methods()`

won’t be able to correctly list the method.

Check *scalarize_pbi.R* for a good example of decomposition routine (e.g., to use as a template).

The strategy for defining the neighborhood structure in the MOEADr package is essentially the same (use Euclidean distances and use the `neighbors$T`

nearest subproblems as a neighborhood). The only difference is the space in which the distances are calculated, which has implications in the need for re-calculating the neighborhood structure. The neighborhoods are defined using an efficient C implementation of the k-nearest-neighbors algorithm available in function `FNN::get.knn`

, which is the only reason why package `MOEADr`

lists `FNN`

in its *Imports* field (see *DESCRIPTION*).

The neighborhood assignment function is `define_neighborhood()`

, which is called directly from the main function `moead()`

.

- INPUTS:
- the neighborhood parameters are defined in the input list
*neighbors*(use`?moead`

and`?define_neighborhood`

to get details on the structure of*aggfun*). Any other required inputs should be explicitly declared in the function definition.

- the neighborhood parameters are defined in the input list
- OUTPUTS:
- the function must output a list object containing the following matrices:
**B**: each row represents the neighborhood of a subproblem as indices (first element is the subproblem index, and the following`neighbors$T - 1`

elements are the neighbor indices). This is a*N x T*matrix.**P**: matrix of probabilities of selection to be used in the sampling of solutions for variation operators. Each element represents the probability of using the solution associated with the*j*-th subproblem when performing a variation operator (e.g., recombination) for the*i*-th subproblem. This is an*N x N*matrix.**fullB**: same as**B**, but assuming that the neighborhood size is equal to the number of subproblems (i.e., requlting in an*N x N*matrix.**fullP**: same as**P**, but with all elements set as`1 / N`

.

- Unlike the previous modules, the neighborhood assignment strategies are defined (in the current version) as options passed to a single function
`define_neighborhood`

. Other possibilities (e.g., to deal with adaptive weights, which would require periodic recalculation) can, at least in principle, use the same strategy. However, if an alternative assignment method becomes too different from the one currently implemented, it may be better to break the options and use the*one function, one file, same name*policy. In this case, the current options should be moved to independent functions starting with a common preffix (as is the case with other modules, e.g., decomposition).

Check *define_neighborhood.R* for the current neighborhood assignment alternatives (e.g., to use as a template).

To discover the available variation operators, use `get_variation_operators()`

. Variation methods are called from within `perform_variation()`

.

- INPUTS:
- The variation operators are defined in the input list
*variation*(use`?moead`

and`?perform_variation`

to get details on the structure of*variation*). Any other required inputs should be explicitly declared in the function definition.

- The variation operators are defined in the input list
- OUTPUTS: the function must output either a matrix
`X`

containing the (modified) points, or a list object containing the matrix`X`

as well as a counter`nfe`

, containing the number of additional function evaluations performed by that operator..

- The name of the function (and of the file) must have the format
*variation_XYZ*, with*XYZ*being the moniker for the contributed method. - the only exceptions to that naming convention are local search operators, which are called from within
`variation_localsearch()`

. Local search methods should follow the naming convention*ls_XYZ*, and available methods are discovered using`get_localsearch_methods()`

. See`?variation_localsearch`

and the*Variation*section of`?moead`

for details. - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_variation_operators()`

and`get_localsearch_methods()`

won’t be able to correctly list the method.

Check *variation_sbx.R* for a good example of a non-local search variation operator, and *variation_localsearch.R* and *ls_dvls.R* for local search methods (e.g., to use as a template).

To discover the available decomposition strategies, use `get_update_methods()`

. Update functions are called from within `update_population()`

.

- INPUTS:
- the update parameters are defined in the input list
*update*(use`?moead`

and`?update_population`

to get details on the structure of*update*). Any other required inputs should be explicitly declared in the function definition.

- the update parameters are defined in the input list
- OUTPUTS:
- the function must output a list object containing the updated matrices
**X**,**Y**, and the updated list**V**.

- the function must output a list object containing the updated matrices

- The name of the function (and of the file) must have the format
*updt_XYZ*, with*XYZ*being the moniker for the contributed method (which is going to be passed as*update$name*). - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_update_methods()`

won’t be able to correctly list the method.

Check *update_standard.R* for a good example of update routine (e.g., to use as a template).

To discover the available constraint handling strategies, use `get_constraint_methods()`

. Constraint handling methods are called from within `order_neighborhood()`

.

- INPUTS:
- the constraint handling parameters are defined in the input list
*constraint*(use`?moead`

to get details on the structure of*constraint*). Any other required inputs should be explicitly declared in the function definition.

- the constraint handling parameters are defined in the input list
- OUTPUTS:
- the function must output a matrix of preference indices, indicating the selection preference relations between solutions (see the
*Value*section of*?constraint_vbr*for details).

- the function must output a matrix of preference indices, indicating the selection preference relations between solutions (see the

- The name of the function (and of the file) must have the format
*constraint_XYZ*, with*XYZ*being the moniker for the contributed method (which is going to be passed as*constraint$name*). - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_constraint_methods()`

won’t be able to correctly list the method.

Check *constraint_penalty.R* for a good example of constraint handling routine (e.g., to use as a template).

The strategies for objective scaling currently available in the MOEADr package are essentially “none” (i.e., no scaling) and “simple” (simple linear scaling to the interval `[0,1]`

).The scaling function is `scale_objectives()`

.

- INPUTS:
- the scaling parameters are defined in the input list
*scaling*(use`?moead`

and`?scale_objectives`

to get details on the structure of*scaling*). Any other required inputs should be explicitly declared in the function definition.

- the scaling parameters are defined in the input list
- OUTPUTS:
- the function must output a list object containing the matrices
**Y**and**Yt**(corresponding to the normalized values of the candidate and incumbent objective function matrices, respectively); as well as two vectors,**minP**and**maxP**, containing the estimates of the ideal and nadir points*for the normalized matrices*(i.e., a vector of`0`

s and a vector of`1`

s, if any scaling different from “none” is used).

- the function must output a list object containing the matrices

- Unlike the previous modules, the scaling strategies are defined (in the current version) as options passed to a single function
`scale_objectives()`

. Other possibilities can, at least in principle, use the same strategy. However, if an alternative assignment method becomes too different from the one currently implemented, it may be better to break the options and use the*one function, one file, same name*policy. In this case, the current options should be moved to independent functions starting with a common preffix (as is the case with other modules, e.g., decomposition).

To discover the available termination criteria, use `get_stop_criteria()`

. Termination methods are called from within `check_stop_criteria()`

.

- INPUTS:
- the stop criteria parameters are defined in the input list
*stopcrit*(use`?moead`

and`?get_stop_criteria`

to get details on the structure of*stopcrit*). Any other required inputs should be explicitly declared in the function definition.

- the stop criteria parameters are defined in the input list
- OUTPUTS:
- the function must output a logical value indicating whether the termination criterion has been reached (
`TRUE`

) or not (`FALSE`

).

- the function must output a logical value indicating whether the termination criterion has been reached (

- The name of the function (and of the file) must have the format
*stop_XYZ*, with*XYZ*being the moniker for the contributed method. - Please follow the
*one function, one file, same name*policy strictly (otherwise`get_stop_criteria()`

won’t be able to correctly list the method.

Check *stop_maxiter.R* for a good example of constraint handling routine (e.g., to use as a template).