Example
This example shows how to use OptimizationParameters to set up the design variables and parameters for an optimization.
- Example
- Creating a dictionary/named tuple of optimization parameters
- Modifying the dictionary/named tuple of optimization parameters
- Assembling the input for the optimization
- Extracting parameters in the optimization
- Printing active design variables during the optimization
- Updating optimization parameters after the optimization
- Writing results to a file
- Updating optimization parameters using a previous optimization
Creating a dictionary/named tuple of optimization parameters
The building block of the entire optimization parameter interface is the OptimizationParameter composite type.
This object stores a number of details about each variable including the variable's:
- Default value(s)
- Lower and upper bound(s) (when used as a design variable)
- Scaling (when used as a design variable)
- Design variable flag(s) indicating whether the parameter is a design variable
- A description of the variable
design_variable1 = OptimizationParameter(1.0, lb=0.0, ub=10.0, scaling=1.0, dv=true, description="A design variable")Instances of OptimizationParameter don't have to be design variables. They can also just be parameters that you would like to be able to change easily between optimization runs. These parameters can be floats, integers, booleans, strings, etc. The only requirement is that if the parameter is an active design variable, it must be continuous.
int_parameter = OptimizationParameter(1)
bool_parameter = OptimizationParameter(true)
string_parameter = OptimizationParameter("foo")In order to associate instances of OptimizationParameter with variable names, we use either a named tuple or a dictionary. Named tuples are preferred for performance, since they provide type information to the compiler, but dictionaries are often more convenient for construction and debugging.
# dictionary of OptimizationParameters
dict = Dict(:design1=>design_variable1, :int1=>int_parameter, :bool1=>bool_parameter, :string1=>string_parameter)
# named tuple of OptimizationParameters
nt = (; :design1=>design_variable1, :int1=>int_parameter, :bool1=>bool_parameter, :string1=>string_parameter)Fortunately, both can be used interchangeably if we use symbols as keys and access optimization parameters by indexing.
# indexing works for both named dictionaries and named tuples
dict[:design1]
nt[:design1]For convenience we provide the functions named_tuple_to_dict and dict_to_named_tuple to convert to and from dictionaries and named tuples.
Perhaps the easiest way to construct a dictionary or named tuple of objects of type OptimizationParameter is to use a CSV input file. The input file for this example has the following structure:
| Parameter | Initial Value(s) | Lower Bound(s) | Upper Bound(s) | Scaling | Design Variable(s)? | Description |
|---|---|---|---|---|---|---|
| # Scalar Parameters | ||||||
| scalar1 | 0 | -Inf | Inf | 1 | false | this variable has all fields filled in |
| scalar2 | 0 | this variable has almost no fields filled in, but is identical to scalar1 | ||||
| scalar3 | 0 | -Inf | Inf | 1 | true | this variable is the same as scalar1, but is activated |
| scalar4 | 250 | 100 | 1000 | 0.002 | true | this variable is scaled to be roughly of order 1 |
| scalar5 | 10 | variables don't have to be design variables, they can also just be parameters | ||||
| scalar6 | -1 | FALSE | note that the case doesn't matter for the design variable flag | |||
| # Vector Parameters | ||||||
| vector1 | [1, 2, 8, 9] | 0 | 10 | 1 | TRUE | this is an example of a vector parameter |
| vector2 | [1, 2, 8, 9] | [0,0,5,5] | [5,5,10,10] | [1,1,0.5,0.5] | TRUE | scalars and/or vectors can be used for any field |
| vector3 | [1, 2, 8, 9] | [0,0,5,5] | [5,5,10,10] | [1,1,0.5,0.5] | [true, false, true, false] | we can even use only part of an array as a design variable |
| vector4 | 0.5*ones(10) | zeros(10) | 1 | ones(10)*0.5 | TRUE | all values can also be input as generic Julia code |
| # Matrix Parameters | ||||||
| matrix1 | [1 3; 2 4] | 0 | 10 | 1 | true | the shape of vectors and matrices will be preserved |
| # Other Parameters | ||||||
| bool1 | true | we can also include other types of variables as design parameters | ||||
| int1 | 1 | integers will be converted to floats when used as design variables, but otherwise left as integers | ||||
| string1 | "foo" | some types of variables like strings cannot be design variables |
Input files like this one may be read into either a dict or a named tuple using read_parameters.
optparams = read_parameters("example.csv")Note that the shape of design variables/parameters is preserved when using OptimizationParameters and that initial values, lower bounds, upper bounds, scaling factors, and design variable flags can be specified using a single value, or an array of values. This makes the OptimizationParameter interface extremely powerful for managing design variables.
Modifying the dictionary/named tuple of optimization parameters
Sometimes it is necessary/convenient to modify the dictionary/named tuple of optimization parameters. This is easily done using the set_x0, set_lb, set_ub, set_dv, and set_description functions. Here we will activate the scalar1 design variable:
optparams = set_dv(optparams, :scalar1, true)Assembling the input for the optimization
Assembling the design variable initial value, lower bound, and upper bound arrays is done using the assemble_input function. This function automatically scales the design variables and centers them around zero.
x0, lb, ub = assemble_input(optparams)Extracting parameters in the optimization
Inside the optimization, OptimizationParameters just needs access to design variable value array and the dictionary/named tuple of optimization parameters. get_values then allows you to extract the parameter values, appropriately modified based on the values in the design variable array as a dictionary/named tuple.
x = rand(length(x0)) # design variable values provided by optimizer
parameters = get_values(optparams, x) # dictionary or named tuple of parameter valuesOrderedCollections.OrderedDict{Symbol, Any} with 14 entries:
:scalar1 => 0.661867
:scalar2 => 0
:scalar3 => 0.854924
:scalar4 => 319.031
:scalar5 => 10
:scalar6 => -1
:vector1 => [0.431854, 0.563946, 0.59526, 0.989816]
:vector2 => [0.943903, 0.436383, 0.0266772, 1.51113]
:vector3 => [0.54783, 2.0, 1.48327, 9.0]
:vector4 => [0.265928, 0.692128, 0.387395, 0.60242, 1.61964, 0.473316, 0.4567…
:matrix1 => [0.216551 0.935189; 0.595121 0.305553]
:bool1 => true
:int1 => 1
:string1 => "foo"These parameter values may be accessed using the common indexing notation for both types discussed previously.
parameters[:scalar1]0.6618668742545061Printing active design variables during the optimization
Active design variables may be printed during the optimization with print_design_variables
print_design_variables(optparams, x)scalar1: 0.6618668742545061
scalar3: 0.8549238267589383
scalar4: 319.031367338548
vector1: [0.43185425104857134, 0.5639464391367315, 0.5952596017185361, 0.9898161777915827]
vector2: [0.9439034349639354, 0.43638341518143065, 0.026677194176234043, 1.5111285104238439]
vector3[1, 3]: [0.5478297690415652, 1.4832686767057075]
vector4: [0.2659284211381088, 0.6921284823996874, 0.3873948978994539, 0.6024200577521086, 1.6196412492424002, 0.4733159263301423, 0.4567504159031939, 0.10222526122633813, 1.062266130159934, 0.547674411222707]
matrix1: [0.21655094204938163 0.9351886675409091; 0.5951213194409107 0.30555343755512754]Updating optimization parameters after the optimization
After the optimization, the optimizer returns a vector of optimal design variables. These variables may be used to replace the optimization parameter initial values using update_parameters
xopt = rand(length(x0)) # optimal design variable values provided by the optimizer
optimized = update_parameters(optparams, xopt) # updates parameter valuesOrderedCollections.OrderedDict{Symbol, OptimizationParameter} with 14 entries:
:scalar1 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar2 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:scalar3 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar4 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar5 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:scalar6 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:vector1 => Design variable OptimizationParameter (type StaticArraysCore.SVec…
:vector2 => Design variable OptimizationParameter (type StaticArraysCore.SVec…
:vector3 => Design variable OptimizationParameter (type StaticArraysCore.SVec…
:vector4 => Design variable OptimizationParameter (type StaticArraysCore.SVec…
:matrix1 => Design variable OptimizationParameter (type StaticArraysCore.SMat…
:bool1 => Constant OptimizationParameter (type Bool, size Tuple{1})…
:int1 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:string1 => Constant OptimizationParameter (type String, size Tuple{1})…Writing results to a file
The updated optimization parameters may then be written to a CSV file using write_parameters.
write_parameters("optimized.csv", optimized)When writing CSV files, a template file (such as the input file) may be used to specify the order of the parameters and to insert comments and blank lines in the resulting file.
write_parameters("example.csv", "optimized.csv", optimized)Updating optimization parameters using a previous optimization
In some cases, we may want to use initial parameter values for design variables that correspond to the optimized design variables found in another optimization run. update_design_variables allows you to update the initial values of the design variables in one set of optimization parameters with the optimized design variables of another set of optimization parameters.
optparams = update_design_variables(optparams, optimized)OrderedCollections.OrderedDict{Symbol, OptimizationParameter} with 14 entries:
:scalar1 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar2 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:scalar3 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar4 => Design variable OptimizationParameter (type Float64, size Tuple{1…
:scalar5 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:scalar6 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:vector1 => Design variable OptimizationParameter (type Vector{Float64}, size…
:vector2 => Design variable OptimizationParameter (type Vector{Float64}, size…
:vector3 => Design variable OptimizationParameter (type Vector{Float64}, size…
:vector4 => Design variable OptimizationParameter (type Vector{Float64}, size…
:matrix1 => Design variable OptimizationParameter (type Matrix{Float64}, size…
:bool1 => Constant OptimizationParameter (type Bool, size Tuple{1})…
:int1 => Constant OptimizationParameter (type Int64, size Tuple{1})…
:string1 => Constant OptimizationParameter (type String, size Tuple{1})…