Example 2: Dido’s problem

In this example, we solve Dido’s problem: find the two-dimensional geometry with biggest area for a given perimeter. Mathematically, this means minimizing the negative area

\[J(\Omega) = \int_\Omega (-1) \,\mathrm{d}\mathbf{x}\]

while keeping the perimeter

\[P(\Omega) = \int_\Omega (-1) \,\mathrm{d}s\]

constant. The solution to the problem is a disc with radius \(r = P(\Omega)/(2\pi)\). In this example, the initial domain is a unit square, which implies that \(P(\Omega)= 4\). We thus aim to reconstruct a disc of radius \(r = 2/\pi \approx 0.636619772367581\) and area \(r^2\pi \approx 1.273239544735163\).

In the following, we describe how to solve this problem in Fireshape. The entire script is contained in the Python file dido.py, which is saved in the Fireshape repository and can be found at the following link: link-to-dido-example.

Import modules

We begin by importing Firedrake, Fireshape, and ROL. We also import fireshop.zoo, which contains utilities to set up the perimeter constraint.

1from firedrake import *
2from fireshape import *
3import fireshape.zoo as fsz
4import ROL

Implement the shape function

To implement the shape function \(J\), we use Fireshape’s class PDEconstrainedObjective. This requires specifying how to evaluate \(J\) in the method PDEconstrainedObjective.objective_value.

 7class NegativeArea(PDEconstrainedObjective):
 8    def __init__(self, *args, **kwargs):
 9        super().__init__(*args, **kwargs)
10
11    def objective_value(self):
12        return assemble(Constant(-1) * dx(self.Q.mesh_m))

Note

Although \(J\) is not technically constrained to a boundary value problem, it is convenient to use the class PDEconstrainedObjective as this automatically returns NaN on poor quality meshes.

Select initial guess, control space, and inner product

We select a unit square centered at \((0.5,0.5)\) as initial domain. To modify the domain, we create a control space of geometric transformations discretized using finite elements of degree 2. (Note the additional input add_to_degree_r=1 in the definition of Q.) To compute descent directions, we employ Riesz representatives of shape derivatives with respect to a full \(H^1\)-inner product. With these, we create a control variable q that will be updated by the optimization algorithm.

15# Select initial guess, control space, and inner product
16mesh = UnitSquareMesh(5, 5)
17Q = FeControlSpace(mesh, add_to_degree_r=1)
18IP = H1InnerProduct(Q)
19q = ControlVector(Q, IP)

Instantiate objective function J

We instantiate \(J(\Omega)\) using the class NegativeArea we have created. During instantiation, we also pass a call back function cb that stores the shape iterates whenever J is evaluated.

21# Instantiate objective function J
22out = VTKFile("domain.pvd")
23J = NegativeArea(Q, cb=lambda: out.write(Q.mesh_m.coordinates))

Instantiate equality constraint P

We instantiate \(P(\Omega)\) using the class SurfaceAreaFunctional from fireshop.zoo.

25# Set up perimeter constraint
26perimeter = fsz.SurfaceAreaFunctional(Q)
27initial_perimeter = perimeter.value(q, None)
28econ = EqualityConstraint([perimeter], target_value=[initial_perimeter])
29emul = ROL.StdVector(1)

Select the optimization algorithm and solve the problem

Finally, we select an augmented Lagrangian optimization algorithm that uses a trust-region optimization algorithm (with l-BFGS Hessian) to solve the augmented Lagrangian subproblems. We carefully select and set the optimization parameters in the dictionary pd. This, together with J, q, econ, and emul are passed to ROL, which solves the problem.

31# Select the optimization algorithm and solve the problem
32pd = {'General': {'Print Verbosity': 0,
33                  'Secant': {'Type': 'Limited-Memory BFGS',
34                             'Maximum Storage': 10}},
35      'Step': {'Type': 'Augmented Lagrangian',
36               'Augmented Lagrangian':
37               {'Use Default Problem Scaling': False,
38                'Constraint Scaling': 1.5,
39                'Subproblem Step Type': 'Trust Region',
40                'Print Intermediate Optimization History': False,
41                'Subproblem Iteration Limit': 10}},
42      'Status Test': {'Gradient Tolerance': 1e-2,
43                      'Step Tolerance': 1e-3,
44                      'Constraint Tolerance': 1e-1,
45                      'Iteration Limit': 10}}
46params = ROL.ParameterList(pd, "Parameters")
47problem = ROL.OptimizationProblem(J, q, econ=econ, emul=emul)
48solver = ROL.OptimizationSolver(problem, params)
49solver.solve()

Result

Typing python3 dido.py in the terminal returns:

 Augmented Lagrangian Solver
Subproblem Solver: Trust Region
  iter  fval           cnorm          gLnorm         snorm          penalty   feasTol   optTol    #fval   #grad   #cval   subIter
  0     -1.000000e+00  0.000000e+00   1.359583e+00                  1.00e+01  1.26e-01  1.36e-02
  1     -1.477728e+00  3.093114e-01   3.244788e-02   4.698565e-01   1.00e+02  1.26e-01  1.00e-01  15      13      24      10
  2     -1.274740e+00  2.943785e-03   1.042650e-01   5.678527e-01   1.00e+02  7.94e-02  1.00e-01  28      23      45      10
  3     -1.272798e+00  1.229090e-04   7.903207e-02   1.968245e-02   1.00e+02  5.01e-02  1.00e-03  38      28      58      7
  4     -1.273378e+00  3.158611e-04   1.664273e-01   5.434476e-02   1.00e+02  3.16e-02  1.00e-03  51      39      80      10
  5     -1.273019e+00  3.230874e-04   1.760829e-02   5.272820e-02   1.00e+02  2.00e-02  1.00e-03  64      49      101     10
  6     -1.273236e+00  4.254024e-06   7.637262e-03   1.757102e-03   1.00e+02  1.26e-02  1.00e-03  77      59      122     10
Optimization Terminated with Status: Converged

This output implies that the retrieved optimal negative area is approximately -1.273236e+00 and that the (perimeter) equality constraint violation is approximately (only) 4.254024e-06.

We can inspect the result by opening the file levelset_domain.pvd with ParaView. In the GIF below, we see that the domain (black grid) converges to the right shape (red circle). Note that the mesh presents bent edges due to using finite elements of degree 2 to discretize domain updates.

Animated GIF created with Pillow