Performing the optimization

We're now ready to perform the optimization, for which we need an Optimizer object. These come in two flavors: a ShapeOptimizer and a FieldOptimizer that respectively act on the shape and a field. We create them with the problem and quantity they're supposed to act on:

// Create shape and field optimizers
var sopt = ShapeOptimizer(problem, m)
var fopt = FieldOptimizer(problem, nn)

Having created these, we can perform the optimizion by calling the linesearch method with a specified number of iterations for each:

// Optimization loop
for (i in 1..100) {  
    fopt.linesearch(20)
    sopt.linesearch(20)
}

Each iteration of a linesearch evolves the field (or shape) down the gradient of the target functional, subject to constraints, and finds an optimal stepsize to reduce the value of the functional. Here, we alternate between optimizing the field and optimizing the shape, performing twenty iterations of each, and overall do this one hundred times. These numbers have been chosen rather arbitrarily, and if you look at the output you will notice that morpho doesn't always execute twenty iterations of each. Rather, at each iteration it checks to see if the change in energy satisfies, $$|E|<\epsilon,$$ or, $$\left|\frac{\Delta E}{E}\right|<\epsilon$$ where the value of \(\epsilon\), the convergence tolerance can be changed by setting the etol property of the Optimizer object:

sopt.etol = 1e-7 // default value is 1e-8

Some other properties of an Optimizer that may be useful for the user to adjust are as follows:

PropertyDefault valuePurpose
etol\(1\times10^{-8}\)Energy tolerance (relative error)
ctol\(1\times10^{-10}\)Constraint tolerance (how well are constraints satisfied)
stepsize0.1Stepsize for relax (changed by linesearch)
steplimit0.5Largest stepsize a linesearch can take
maxconstraintsteps20Number of steps the optimizer may take to ensure constraints are satisfied
quietfalseWhether to print output as the optimization happens