Coding Optimizers for use in Rave

From Rave Documentation
Jump to: navigation, search

Introduction

This page contains some guidelines for how to code optimizers for use in Rave. Like other Rave plugins, optimizers require you to follow certain code structures in a few places, but for the most part you can code the actual optimization part of it however you want.


Required Files

Just like workspace objects, each optimizer has a keyword that serves as its unique identifier in Rave. Keywords appear in the name of the folder that contains the files for your optimizer, and in the filenames themselves. There is no min/max length requirement for the keyword, and it can contain letters and numbers. Keywords are case sensitive, so if you use capitalization, be sure to use it everywhere.

The optimizer described in this example has the keyword "KEYWORD".

All files related to this optimizer must be placed in a folder named KEYWORD, which in turn must be placed in one of the three subfolders in the rave\optimizers directory. The subfolders are rave\optimizers\single, rave\optimizers\multi, and rave\optimizers\ranking. Put single objective optimizers in the single directory, multiobjective optimizers in the multi directory, and ranking functions in the ranking directory. Ranking functions are any functions that act only on the data set already loaded in Rave without requiring new function calls to generate new data. (They might, for example, select some rows in the data set based on some definition of optimality.)

The choice of which directory you place your optimizer in determines where it appears in the Algorithm menu on the Optimize tab. (This is its only effect.) Also, if you place it in the single directory, Rave will only allow one objective to be selected from the Objective list on the Optimize tab. If you place it in the multi or ranking directories, the user may be allowed to select multiple objectives from the Objective list, as defined by settings.maxobjectives (described below).

Once you've made your KEYWORD directory, you must put three required files in it, plus any other helper files that your optimizer uses. The three required files are:

  • optimizerinfo.txt - A plain text file that describes the optimizer. (Note that the file name does not contain KEYWORD). The first line of this file is the name of your optimizer as you want it to appear in Rave. The second line is a short description of the optimizer that will appear in the infobar when this optimizer is selected from the Algorithm menu. Any additional lines comprise a "help file" for your optimizer.
  • optimizersettingsKEYWORD.m - This is a script that defines any user-specified parameters that your optimizer needs, and a few required parameters that Rave needs.
  • raveoptKEYWORD.m - This is the main function that actually runs your optimizer

The required files are describe in more detail below.

optimizerinfo.txt

This is a simple two+ line plain text file. It doesn't contain any code, just plain English text. The first line contains the name of the optimizer as it will appear in the Algorithm menu on the Optimize Tab. Because the menu is only 200 pixels wide, try to keep this to around 40 characters or less. The second line of the file contains a slightly longer description. This should contain critical identifying information about the optimizer, not a description of how it works. If the optimizer requires any MATLAB toolboxes (or other files that must be downloaded etc) you should note it here. You might also want to put your name here.

In the remainder of this file (lines 3+), you can put any instructions or additional information about the optimizer. Future versions of Rave will offer a method for the user to view this information from the Optimize Tab.


optimizersettingsKEYWORD.m

This file defines the parameters that will appear in the Settings table on the Optimize Tab when this optimizer is selected from the Algorithm menu, and also defines values for few parameters Rave uses to control other aspects of the Optimize Tab when this algorithm is selected from the Algorithms Menu. This file is just a list of lines (i.e. a MATLAB script, not a function) where each line defines one setting. The settings should be listed in the order that you wish them to appear in the Settings Table. The required Rave parameter settings do not appear in the Table, so you can put them wherever you want in this file. Because this is a MATLAB script, you can optionally include comments describing the settings.


The format for the required Rave parameters is: settings.PARAMETERNAME=PARAMETERVALUE;


The required parameters must be included in this file. If these are missing, your Optimizer will not run. Each of these parameters controls only how the Optimize Tab user interface should act. It is up to you to actually code your optimizer to match whatever behavior you define using these parameters. The required parameters are:

  • settings.enforcesideconstraints = 0 or 1 or 2 This parameter identifies whether the optimizer will enforce the upper/lower bounds on each independent variable defined by handles.maxlimit and handles.minlimit. If = 0, the bounds will not be enforced. If = 1, the bounds will be enforced. If = 2, the optimizer supports both approaches and the user can specify whether or not to enforce them by checking the "Enforce side constraints" box on the optimize tab.
    • The value you specify here only controls the appearance of the "Enforce side constraints" check box on the Optimize Tab. It is up to you to code the actual enforcing (or ignoring) of the side constraints in the raveoptKEYWORD.m file.
    • Note: If you set this value = 2 in this file, then when the user runs this optimizer its settings structure will have settings.enforcesideconstraints = 0 or 1 depending on whether the user checked the box or not. Thus you use this same parameter within the raveoptKEYWORD.m file to determine whether to enforce the side constraints for each run.
  • settings.allowconstraints = 0 or 1 This parameter identifies whether your optimizer supports general constraints that the user may define on the constrain tab and select for enforcement on the Optimize Tab. If = 0, constraints are not supported and the constraint menu on the Optimize Tab will be made inactive. If = 1, constraints are supported and the user can select them on the list in the Optimize tab.
    • The value you specify here only controls the appearance of the Constraint Menu on the Optimize Tab. It is up to you to code the actual enforcing of constraints in your raveoptKEYWORD.m file.
  • settings.numberofobjectives = a two-element vector of a positive integers or inf This parameter controls the maximum number of objectives the user is allowed to select from the Objective Menu on the Optimize Tab. This parameter is only used by multi and ranking optimizers (i.e. optimizers located in the multi or ranking subdirectories of rave\optimizers). The first element of this vector defines the minimum allowable number of objectives, and the second element defines the maximum. For example, if this = [1 inf], then the optimizer supports any number of objectives. If this = [2 4], then the optimizer only supports two, three, or four objectives. If this = [2 2] then the optimizer requires exactly two objectives. If the user selects a number of objectives from the Objective Menu outside of this range, they will see an error dialog box when the try to run the optimizer telling them that they have selected too few or too many objectives, and the optimizer will not run.
    • The value you specify here only controls the limits that rave enforces before your optimizer begins running. It is up to you to actually code support for whatever number of objectives you specify here in your raveoptKEYWORD.m file.
  • settings.isstoppable = 0 or 1 This parameter identifies whether or not Rave should display a stop button when the optimizer is running. If = 1, a stop button will be shown. If = 0, no stop button will be shown.
    • The value you specify here only controls whether or not the stop button appears. It is up to you to code your optimizer to actually stop when the stop button is pressed.

In addition to these required parameters, you can include user-specified parameters that will appear in the Settings Table.

The format for user-specified parameters is:

settings.PARAMETERNAME = {'FORMATTEDNAME';'INITIALVALUE'};

Here, PARAMETERNAME is the variable name that you will use to refer to this parameter in the raveoptKEYWORD.m file. This is not displayed in Rave, but you will use it in your code. FORMATTEDNAME is the name as it will appear to the user. This must be a string and may contains spaces or any other formatting you like. INITIALVALUE is the initial value that will appear as the default value in the settings table. This may be either a string or a scalar number. If you enter a string here, and the user changes it in the table, whatever they enter in the table will also be treated as a string. If you enter a number here, and the user changes it in the table, whatever they enter will be converted to a number. Because all numbers can be converted to strings, but not all strings can be converted to numbers, any parameter that can take string or numerical values should be listed in this file as a string.

It is recommended that you make the PARAMETERNAME similar to the FORMATTEDNAME to make your code more readable.

Here are some examples:

settings.pop={'Population',100}; 

This line defines a variable named settings.pop with the initial value of 100. This will appear in the Settings Table with the label Population. Because 100 is entered as a number (ie it is not in quote), the user can only change this value to another number. If they enter, for example, "hello" in this box, it will be converted to NaN before the optimizer is run.

settings.maxtime={'Time Limit','60'}; 

This line defines a variable named settings.maxtime with the initial value of '60'. Because '60' is a string, the user will be allowed to enter anything in this box, and Rave will just set settings.maxtime equal to exactly the string that the enter. Then in your raveoptKEYWORD.m file you can parse this string and determine whether to convert it to a number etc.

Parameters Defined by the Objective Tab

The settings that the user specifies on the Objective Tab are also stored in the settings structure. You don't need to include these in your optimizersettingsKEYWORD.m file (in fact, you definitely should not include these; see the section below on reusing parameter names.)

You will use these settings in your raveoptKEYWORD.m file to control the optimizer, so it's important that you know what they are called and how their values are interpreted. These parameters are:

  • settings.src = handles.fig. You use this as an input to guidata to load/save the handles structure
  • settings.method = KEYWORD. Rave stores KEYWORD here as a record of what algorithm is being run. You can refer to this in helper functions to check what algorithm is running. For example, you might create an animation function that works for multiple optimizers, so you can check this value to customize its behavior depending on what optimizer is being run. (See e.g. raveanimatemultiobj.m for an example).
  • settings.d = the index of the dataset being optimized
  • settings.a = the index of the analysis being used to define independent variable ranges and any other Analysis Properties that may be needed.
  • settings.x = the index (or indices) of the objectives that are selected in the objective menu. These are the columns of the dataset that are being optimized.
  • settings.funvars = the indices of the independent variables that are inputs to the functions that calculate settings.x. These are used as the independent variables in the optimization.
  • settings.plotprogress = 1 if the "Animate Progress" box is checked, and =0 if it is unchecked. This value is just to let you know whether or not the user requested animation. It is still up to you to code the animation itself!
  • settings.plotaxes = the handle of the graph (or graphs) on which the animation will be drawn. These are elements of handles.allgraphs.
  • settings.plottype = a cell array of axes formats for the graphs in settings.plotaxes. These are the values stored in handles.graphinfo.(graphtype).axesformat. (i.e. 'single','3D','matrix', etc)
  • settings.plotvars = a list of variables that must be evaluated in order to animate the progress. These are generally the variables that are displayed on the axes of settings.plotaxes
  • settings.plotinputs = a list of the independent variables that are inputs to the functions in settings.plotvars.
  • settings.animation = 0 or 1 indicating whether or not to save the animation to a file.
  • settings.targets = handles.targetvalues{d}(x); The target values of the objectives (usually inf or -inf).
  • settings.activeconstraints = an index into the Constraint Menu on the Optimize Tab indicating which constraints are selected to be enforced. A 0 in this list indicates the blank space at the top of the constraint menu. (You should do a setdiff to remove 0 from this array before you use its value).
  • settings.startbutton - the handle of the start button on the Objective Tab. You can use this if you need to enable/disable the button in response to some condition/event.
  • settings.stopbuttonpos - the position to display the stop button. Currently this is directly on top of the Start button.
  • settings.updatexdials - Currently = true by default; in the future this will control an aspect of how optimizers are animated.
  • settings.x0 - The starting point for the optimizer; currently = handles.xdials{d}{a}{settings.funvars}, normalized to 0-1 range.

Reusing Paramter Names

Every parameter must have a unique name, so you cannot reuse the names of any of the standard parameters listed above. To construct the settings structure, Rave first loads these standard paramters and their values and then appends whatever is contained in your optimizersettingsKEYWORD.m file. Thus any parameters listed in your optimizersettingsKEYWORD.m will overwrite the standard parameters if their names match. You almost certainly want to avoid this, but if you wanted to somehow override Rave's default behaviors, this would be one way to do it.

The creation of the settings structure happens in the function "create_settings" in optimizetab.m.

raveoptKEYWORD.m

The raveoptKEYWORD.m file is the file that actually runs the optimizer and returns its results. This file must contains a function that is also named raveoptKEYWORD (this must be the first function in the file. If this function needs to call subfunctions, you can put them in this file too, or you can put them in their own files.

Required Function Parts

There are only two rules you need to follow when making the raveoptKEYWORD.m file. First, the file's main function must have one of these two formats for its signature:

 results = raveoptKEYWORD(src,ev,settings) 

or

 results = raveoptKEYWORD(src,ev,settings,overload) 

The first signature is the basic version that most optimizers will use. As always in Rave, the src input is handles.fig, which you can use as an input to guidata to retrieve the handles structure. The second input, ev, is empty. The third input is the settings structure.

The second signature is the "advanced" option that lets other optimizers build off of this optimizer. This option is described in the section Coding Advanced Optimizers below.


The second rule is that at the end of the file, your function must send certain data back to Rave. This data is in the form of a structure, named "results". At a minimum, the results structure must include three fields:

  • results.settings = settings; Put this line at the end of your file so that any changes to the settings structure made by this function are preserved in the results structure.
  • results.finalinputs = the final values of the independent variables that are inputs to the objective function(s). These variables' indices are listed in settings.funvars, and this array must contain as many columns as numel(settings.funvars). If the optimizer returns only a single best point, this array has one row. If the optimizer returns multiple points, all of which are in some sense optimal, this array should have each of those points in its own row. This array should not contain suboptimal points.
  • results.finalobjective = the final value(s) of the objective(s). If there is more than one objective, each goes in its own column. This array should have as many rows as results.finalinputs has.

In addition to these required fields, you can include any additional fields you want, but Rave will not use them directly. If you want to save additional information, these field names are recommended for future compatibility:

  • If you want to store the complete history of the optimizer, put it in a field called results.inputshistory. Make this a 1-D cell array, where each cell contains the values of the independent variables at the start of the i'th iteration of the optimizer. (1st cell contains the initial values). Use this to store the values of independent variables for the complete iteration history of an optimizer. (Using a cell array allows a different number of rows to be recorded for each iteration, in case the optimizer has a population that grows/shrinks over time.
  • If you want to store suboptimal final values, put these in results.finalinputssuboptimal and results.finalobjectivesuboptimal. Format these just like results.finalinput and results.finalobjective respectively. For example, if you are coding a genetic algorithm, you can return the best final population member(s) in results.finalinput/finalobjective, and return the remaining non-optimal population members in results.finalinputssuboptimal and results.finalobjectivesuboptimal.

Just so we're clear: Rave does not currently use any fields except settings, finalinputs, and finalobjectives. But in the future if we expand Rave to have more types of reports for viewing optimization results, we will use the field names listed below.

ravemakefilenameKEYWORD.m (optional)

You can optionally include a function named ravemakefilenameKEYWORD.m. This function takes a single input (settings) and returns a single output (filename). When users export optimization results to a file, Rave will use this function to generate the default filename. If this function does not exist, Rave will use a generic filename. You can use this function to make descriptive file names based on the information contained in the settings structure. For example, the filename could list the variables being optimized and some other key parameters that are relevant to your work.

Don't be afraid to edit this function to meet your current needs!

Helper Functions

Rave comes with some helper functions to perform common tasks related to optimization. You can call these functions from any optimizers you write so that you don't need to recreate their capabilities. Because these functions are used by many optimizers, you should NEVER alter their behavior or input/output structure/format or you will almost certainly break other optimizers. If you need to make a small tweak to their behavior, just make a copy of the file, give it a new function name, and edit that.

Unlike most rave functions, these actually have help sections at the top of their source code, so their use is not explained in detail here. If one of these sounds useful to you, view its source code to see exactly how it works.


  • ravemakeminimizefunction - Makes a new .m file that contains a function in the standard MATLAB objective function format (i.e. the format required for objective functions by the Optimizer Toolbox). The function is formatted/transformed so that the goal is to minimize this function. For example, if the user specified an objective to be maximized, it will be multiplied by -1 so that now it should be minimized. The function created will also be normalized so that each independent variable ranges from 0-1. This helps some optimizers perform better, especially when the true ranges of the independent variables have different orders of magnitude.
  • ravenonlcon - Calculates the current values of all constraints (the constraints selected in the list on the Objective Tab, not side constraints) and returns their values in the standard Optimization Toolbox format (two vectors, c and ceq). Note that this function takes many inputs, whereas the Optimization Toolbox requires that constraint-calculating functions take only a single input (the current point, x). Consequently, before you can use this function with an optimization toolbox algorithm, it must be wrapped with an anonymous function that takes only the first input x. (See ravefmincon.m for an example, search for the variable name "anonnonlcon".)
  • raveinitializeoptimizeranimation - A short script that checks if the user requested to save the animation to a file. If so, this also sets up the file export part of the animation. (It does not do the actual animating.) Call this near the beginning of your raveoptKEYWORD.m file.
  • ravefinishoptimizeranimation - A short script that checks if the user requested to save the animation to a file. If so, this finishes exporting the animation. Call this near the end of your raveoptKEYWORD.m file.

Recommended Coding Practices

Although you can code the inner workings of your optimizer however you want, we recommend the following practices to make it easier for people to understand and expand your code, and to make it extensible with future development as described in the "Coding Advanced Optimizers" section below.

  • Have your optimizer construct and use an objective function that it references with a function handle. Make this function follow the standard Optimization Toolbox format: the function should take in an array of the independent variable values and output an array of the objective values. You can easily accomplish this by using ravemakeminimizefunction to construct your objective function.
  • If your objective function supports constraints (beyond the typical upper/lower limits on each independent variable), have it calculate the constraint values with a function that is referenced by a function handle and that follows the standard Optimization Toolbox format: the function should take in an array of independent variable values and output two arrays, c and ceq that contain the values of the inequality and equality constraints respectively. You can easily accomplish this by using ravenonlcon to calculate your constraints.

Calling One Optimizer from Another

By constructing an appropriate settings structure, it is possible to call one optimizer from another optimizer in order to create higher-level optimizers that slightly tweak the behavior of whatever optimizer(s) they call. For example, the ravefmincon optimizer simply calls fmincon starting at the point defined by handles.xdials{d}{a}. You might want to create another optimizer that calls fmincon several times in a row, each time starting at a different point in the design space. This can be accomplished by coding an optimizer that calls ravefmincon in a loop with a different value of settings.x0 each time.

There are some limits to what can be accomplished with the settings structure, however. Since optimizers generally construct their objective functions from settings.y, they are limited to objective functions that are columns in your data set. Similarly they construct constraint functions from settings.activeconstraints, so they are limited to constraints defined in the data set.

In order to give you more flexibility to call optimizers with ANY objective function or constraints, even ones that are not defined in your handles structure, you can code your optimizer to take in a fourth variable, named "overload". (Of course you could call this whatever you want, but for consistency we recommend the name overload.) Overload is a structure, like settings, with the following fields:

  • overload.objectivefcn = a handle to a function to be used as the objective function. Obviously this function must output values in whatever format your optmizier normally expects. If your optimizer does not use a function handle to define its objective function, then you cannot use this overload option.
  • overload.constraintfcn = a handle to a function that calculates constraint values and outputs them in the standard c, ceq format used by the Optimization Toolbox.
  • overload.mins and overload.maxs = vectors of length n, where n = number of independent variables to overload.objectivefcn that define the lower and upper limits for the independent variables.