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 values
OrderedCollections.OrderedDict{Symbol,Any} with 14 entries:
:scalar1 => 0.87023
:scalar2 => 0
:scalar3 => 0.408729
:scalar4 => -150.655
:scalar5 => 10
:scalar6 => -1
:vector1 => [5.05975, 5.93482, 5.43771, 5.21374]
:vector2 => [3.02534, 2.66725, 9.3686, 8.583]
:vector3 => [3.41492, 2.0, 8.82073, 9.0]
:vector4 => [2.04814, 1.31263, 1.76156, 1.35801, 2.27213, 0.519832, 2.16864, …
:matrix1 => [5.17458 5.85777; 5.37459 5.87482]
: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.870230255947513
Printing 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.870230255947513
scalar3: 0.4087291102882129
scalar4: -150.65494184516643
vector1: [5.059748080017826, 5.934824616128701, 5.437711611869744, 5.213737837436361]
vector2: [3.0253379651879384, 2.6672515058479904, 9.368595649264924, 8.582995567378884]
vector3[1, 3]: [3.414918024630383, 8.82073446150302]
vector4: [2.0481392700799534, 1.3126299921633384, 1.7615583668208248, 1.3580113862775214, 2.2721265770702663, 0.5198316374466505, 2.1686384388636624, 0.7868872665788857, 1.9576749036642829, 1.211045602127271]
matrix1: [5.174583193756317 5.857770346679452; 5.37458874764173 5.874815234872957]
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 values
OrderedCollections.OrderedDict{Symbol,OptimizationParameter} with 14 entries:
:scalar1 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(0.57629…
:scalar2 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(0, -Inf,…
:scalar3 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(0.71910…
:scalar4 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(-391.61…
:scalar5 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(10, -Inf…
:scalar6 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(-1, -Inf…
:vector1 => OptimizationParameter{Tuple{4},SArray{Tuple{4},Float64,1,4},Array…
:vector2 => OptimizationParameter{Tuple{4},SArray{Tuple{4},Float64,1,4},Array…
:vector3 => OptimizationParameter{Tuple{4},SArray{Tuple{4},Float64,1,4},Array…
:vector4 => OptimizationParameter{Tuple{10},SArray{Tuple{10},Float64,1,10},Ar…
:matrix1 => OptimizationParameter{Tuple{2,2},SArray{Tuple{2,2},Float64,2,4},A…
:bool1 => OptimizationParameter{Tuple{1},Bool,Float64,Bool,false}(true, -In…
:int1 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(1, -Inf,…
:string1 => OptimizationParameter{Tuple{1},String,Float64,Bool,false}("foo", …
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 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(0.57629…
:scalar2 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(0, -Inf,…
:scalar3 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(0.71910…
:scalar4 => OptimizationParameter{Tuple{1},Float64,Float64,Bool,true}(-391.61…
:scalar5 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(10, -Inf…
:scalar6 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(-1, -Inf…
:vector1 => OptimizationParameter{Tuple{4},Array{Float64,1},Array{Int64,1},Bi…
:vector2 => OptimizationParameter{Tuple{4},Array{Float64,1},Array{Float64,1},…
:vector3 => OptimizationParameter{Tuple{4},Array{Float64,1},Array{Float64,1},…
:vector4 => OptimizationParameter{Tuple{10},Array{Float64,1},Array{Float64,1}…
:matrix1 => OptimizationParameter{Tuple{2,2},Array{Float64,2},Array{Int64,2},…
:bool1 => OptimizationParameter{Tuple{1},Bool,Float64,Bool,false}(true, -In…
:int1 => OptimizationParameter{Tuple{1},Int64,Float64,Bool,false}(1, -Inf,…
:string1 => OptimizationParameter{Tuple{1},String,Float64,Bool,false}("foo", …