#. # Optimizing the length of a tuning fork
#. 
#.  To illustrate how to use FeenoX in an optimization loop, let us consider
#. the problem of finding the length\ $\ell_1$ of a tuning fork (@fig:fork-meshed)
#. such that the fundamental frequency on a free-free oscillation is equal to the
#. base\ A frequency at\ 440\ Hz.
#. 
#. ![What length\ $\ell_1$ is needed so the fork vibrates at 440\ Hz?](fork-meshed.svg){#fig:fork-meshed width=20%}
#. 
#. The FeenoX input is extremely simple input file, since it has to solve the free-free mechanical modal
#. problem (i.e. without any Dirichlet boundary condition). All it has to do is to print the fundamental frequency.
#.
#. To find the length\ $\ell_1$, FeenoX is sucessively called from a Python driving script
#. called [`fork.py`](https://github.com/seamplex/feenox/blob/main/examples/fork.py). This script
#. uses Gmsh's Python API to create the CAD and the mesh of the tuning fork given the geometrical
#. arguments $r$, $w$, $\ell_1$ and $\ell_2$. The parameter $n$ controls the number of elements through
#. the fork's thickness. Here is the driving script without the CAD & mesh details
#. (the full implementation of the function is available in the examples directory of the FeenoX distribution):
#.
#. ```python
#. import math
#. import gmsh
#. import subprocess  # to call FeenoX and read back
#. 
#. def create_mesh(r, w, l1, l2, n):
#.   gmsh.initialize()
#.   ...
#.   gmsh.write("fork.msh")  
#.   gmsh.finalize()
#.   return len(nodes)
#.   
#. def main():
#.   target = 440    # target frequency
#.   eps = 1e-2      # tolerance
#.   r = 4.2e-3      # geometric parameters
#.   w = 3e-3
#.   l1 = 30e-3
#.   l2 = 60e-3
#. 
#.   for n in range(1,7):   # mesh refinement level
#.     l1 = 60e-3              # restart l1 & error
#.     error = 60
#.     while abs(error) > eps:   # loop
#.       l1 = l1 - 1e-4*error
#.       # mesh with Gmsh Python API
#.       nodes = create_mesh(r, w, l1, l2, n)
#.       # call FeenoX and read scalar back
#.       # TODO: FeenoX Python API (like Gmsh)
#.       result = subprocess.run(['feenox', 'fork.fee'], stdout=subprocess.PIPE)
#.       freq = float(result.stdout.decode('utf-8'))
#.       error = target - freq
#.     
#.     print(nodes, l1, freq)
#. ```
#. 
#. Note that in this particular case, the FeenoX input files does not expand any command-line argument.
#. The trick is that the mesh file `fork.msh` is overwritten in each call of the optimization loop.
#. The detailed steps between `gmsh.initialize()` and `gmsh.finalize()` are not shown here,
#.
#. Since the computed frequency depends both on the length\ $\ell_1$ and on the mesh refinement level\ $n$,
#. there are actually two nested loops: one parametric over $n=1,2\dots,7$ and the optimization loop
#. itself that tries to find\ $\ell_1$ so as to obtain a frequency equal to 440\ Hz within 0.01% of error.
#$ $ python fork.py > fork.dat
#$ $ pyxplot fork.ppl
#$ $
PROBLEM modal 3D MODES 1  # only one mode needed
READ_MESH fork.msh  # in [m]
E = 2.07e11         # in [Pa]
nu = 0.33
rho = 7829          # in [kg/m^2]

# no BCs! It is a free-free vibration problem
SOLVE_PROBLEM

# write back the fundamental frequency to stdout
PRINT f(1)
#> ![Estimated length\ $\ell_1$ needed to get 440\ Hz for different mesh refinement levels\ $n$](fork.svg){#fig:fork}
