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. 1) such that
the fundamental frequency on a free-free oscillation is equal to the
base A frequency at 440 Hz.
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.
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):
import mathimport gmshimport subprocess # to call FeenoX and read backdef create_mesh(r, w, l1, l2, n): gmsh.initialize() ... gmsh.write("fork.msh") gmsh.finalize()returnlen(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-3for n inrange(1,7): # mesh refinement level l1 =60e-3# restart l1 & error error =60whileabs(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 - freqprint(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.
PROBLEM modal 3D MODES1# only one mode neededREAD_MESH fork.msh # in [m]E=2.07e11# in [Pa]nu=0.33rho=7829# in [kg/m^2]# no BCs! It is a free-free vibration problemSOLVE_PROBLEM# write back the fundamental frequency to stdoutPRINT f(1)
$ python fork.py > fork.dat
$ pyxplot fork.ppl
$
2 Five natural modes of a
cantilevered wire
Back in College, we had this
subject Experimental Physics 101. I had to measure the natural modes of
two cantilevered wires and determine the Young modulus of of those
measurements. The report is here.
Two comments:
It is in Spanish
There was a systematic error and a factor of two sneaked in into the
measured values
Here is a finite-element version of the experimental setup with a
comparison to then theoretical values written directly as Markdown
tables. The material (either aluminum or copper) and the mesh type
(either tet or hex) and be chosen at runtime through command line
arguments.
DEFAULT_ARGUMENT_VALUE1 hex # mesh, either hex or unstructDEFAULT_ARGUMENT_VALUE2 copper # material, either copper or aluminuml =0.5*303e-3# cantilever wire length [ m ]d =1.948e-3# wire diameter [ m ]# material properties for copperm_copper =0.5*8.02e-3# total mass (half the measured because of the experimental disposition) [ kg ]E_copper =2*66.2e9# [ Pa ] Young modulus (twice because the factor-two error)# material properties for aluminumm_aluminum =0.5*2.67e-3E_aluminum =2*40.2e9# problem’s propertiesE= E_$2# [ MPa ]rho= m_$2/(pi*(0.5*d)^2*l)# [ kg / m^3 ] density = mass (measured) / volume nu=0# Poisson’s ratio (does not appear in Euler-Bernoulli)# analytical solutionVECTOR kl[5]VECTOR f_euler[5]# first compute the first five roots ok cosh(kl)*cos(kl)+1 kl[i] =root(cosh(t)*cos(t)+1, t, 3*i-2,3*i+1)# then compute the frequencies according to Euler-Bernoulli# note that we need to use SI inside the square rootA =pi*(d/2)^2I =pi/4*(d/2)^4f_euler[i] =1/(2*pi)* kl(i)^2*sqrt((E* I)/(rho* A * l^4))# now compute the modes numerically with FEM# note that each mode is duplicated as it is degeneratedREAD_MESH wire-$1.msh DIMENSIONS3PROBLEM modal MODES10BC fixed fixedSOLVE_PROBLEM# github-formatted markdown table# compare the frequenciesPRINT " \$n\$ | FEM [Hz] | Euler [Hz] | Relative difference [%]"PRINT ":------:|:-------------:|:-------------:|:--------------:"PRINT_VECTORSEP "\t|\t" i %.4g f(2*i-1) f_euler %.2f 100*(f_euler(i)-f(2*i-1))/f_euler(i)PRINTPRINT ": $2 wire over $1 mesh"# commonmark tablePRINTPRINT " \$n\$ | \$L\$ | \$\\Gamma\$ | \$\\mu\$ | \$M\$"PRINT ":------:+:---------------------:+:---------------------:+:-------------:+:--------------:"PRINT_VECTORSEP "\t|\t" i "%+.1e" L Gamma "%.4f" mu Mu PRINTPRINT ": $2 wire over $1 mesh, participation and excitation factors \$L\$ and \$\\Gamma\$, effective per-mode and cummulative mass fractions \$\\mu\$ and \$M\$"# write the modes into a vtk fileWRITE_MESH wire-$1-$2.vtk \VECTOR u1 v1 w1 VECTOR u2 v2 w2 VECTOR u3 v3 w3 \VECTOR u4 v4 w4 VECTOR u5 v5 w5 VECTOR u6 v6 w6 \VECTOR u7 v7 w7 VECTOR u8 v8 w8 VECTOR u9 v9 w9 VECTOR u10 v10 w10# and into a msh fileWRITE_MESH wire-$1-$2.msh { u1 v1 w1 u2 v2 w2 u3 v3 w3 u4 v4 w4 u5 v5 w5 u6 v6 w6 u7 v7 w7 u8 v8 w8 u9 v9 w9 u10 v10 w10}