main.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // This code simulates the steady-state vibration of a 2D CMUT (capacitive micromachined
  2. // ultrasonic transducer). A CMUT is a micromembrane, actuated with an electrostatic
  3. // force, that vibrates in a fluid (water here). Due to the small dimensions at play
  4. // the electric excitation frequency is in the megahertz range.
  5. //
  6. // In this code the electric excitation is a sine wave so that the vibration is
  7. // periodic. Due to the electroelastic nonlinearity however the membrane vibration
  8. // and the pressure field includes new harmonics. These harmonics are computed
  9. // with the multiharmonic resolution method in which all calculations are brought
  10. // back to the undeformed configuration as detailed in the paper
  11. // "Steady-State, Nonlinear Analysis of Large Arrays of Electrically Actuated
  12. // Micromembranes Vibrating in a Fluid" (A Halbach, C Geuzaine).
  13. #include "sparselizardbase.h"
  14. using namespace mathop;
  15. void sparselizard(void)
  16. {
  17. // The domain regions as defined in 'cmut2d.geo':
  18. int insulator = 1, pillars = 2, vacuumgap = 3, membrane = 4, fluid = 5, ground = 6, clamp = 6, electrode = 7, fluidboundary = 8;
  19. // The mesh can be curved!
  20. mesh mymesh("cmut2d.msh");
  21. // Define the region for the mechanical problem:
  22. int solid = regionunion({insulator, pillars, membrane});
  23. // Define the region for the electric problem:
  24. int electricdomain = regionunion({insulator, pillars, membrane, vacuumgap});
  25. // Define the membrane top line:
  26. int membranetop = regionintersection({membrane, fluid});
  27. // Nodal shape functions 'h1' for the electric potential field v, acoustic pressure
  28. // field p and membrane deflection u (u has components x and y).
  29. // umesh smoothly deforms the mesh in the vacuum gap for a given membrane deflection.
  30. //
  31. // Multiharmonic fields are used. This means that the time-dependency of a field h
  32. // is approximated by a truncated Fourier series:
  33. //
  34. // h = h1 + h2*sin(2pi*f0*t) + h3*cos(2pi*f0*t) + h4*sin(2 * 2pi*f0*t) + h5*cos(2 * 2pi*f0*t) + ...
  35. //
  36. // where f0 is the fundamental frequency, t is the time variable and hi are the Fourier coefficients.
  37. // The second argument in the field declaration lists all harmonics considered in the truncation.
  38. // For example the displacement field u is supposed to be well approximated by
  39. // u = u1 + u4*sin(2 * 2pi*f0*t) + u5*cos(2 * 2pi*f0*t) while in the pressure field the constant is removed.
  40. //
  41. field u("h1xy",{1,4,5}), umesh("h1xy",{1,4,5}), v("h1",{2,3}), p("h1",{4,5});
  42. // Set the fundamental frequency f0 to 6 MHz:
  43. setfundamentalfrequency(6e6);
  44. // Since this is a multiharmonic resolution on a mesh deformed by a multiharmonic
  45. // displacement field the calculations must be brought back on the undeformed
  46. // configuration with a variable change [x y z]deformed = [x y z]undeformed + umesh.
  47. //
  48. // This is the Jacobian matrix for the change of variable:
  49. //
  50. expression jac = array2x2(1+dx(compx(umesh)),dx(compy(umesh)), dy(compx(umesh)),1+dy(compy(umesh)));
  51. expression invjac = inverse(jac);
  52. expression detjac = determinant(jac);
  53. // The expressions above might appear multiple times in a formulation
  54. // and reusing them while generating a formulation will give a speedup:
  55. invjac.reuseit(); detjac.reuseit();
  56. // Use interpolation order:
  57. //
  58. // - 3 for u on the membrane
  59. // - 2 for u on the pillars
  60. // - 1 elsewhere
  61. //
  62. // - 2 for the pressure field p
  63. //
  64. // - 1 for the electric potential v
  65. //
  66. // Default order is 1.
  67. u.setorder(pillars, 2);
  68. u.setorder(membrane, 3);
  69. p.setorder(fluid, 2);
  70. // Clamp and ground all harmonics (i.e. 0 valued-Dirichlet conditions for u and v):
  71. u.setconstraint(clamp);
  72. v.setconstraint(ground);
  73. // Force the electric potential on the electrode to 250*sin(2pi*f0*t).
  74. // First set all v harmonics to 0 then set harmonic 2 to 250:
  75. v.setconstraint(electrode);
  76. v.harmonic(2).setconstraint(electrode, 250);
  77. // E is Young's modulus. nu is Poisson's ratio. rho is the volumic mass.
  78. // epsilon is the electric permittivity.
  79. //
  80. // The membrane is polysilicon, the insulator is silicon dioxyde.
  81. parameter E, nu, rho, epsilon;
  82. E|insulator = 70e9;
  83. E|pillars = 150e9;
  84. E|membrane = 150e9;
  85. nu|insulator = 0.17;
  86. nu|pillars = 0.3;
  87. nu|membrane = 0.3;
  88. rho|insulator = 2200;
  89. rho|pillars = 2330;
  90. rho|membrane = 2330;
  91. rho|fluid = 1000;
  92. epsilon|vacuumgap = 8.854e-12;
  93. epsilon|insulator = 3.9*8.854e-12;
  94. epsilon|pillars = 11.7*8.854e-12;
  95. epsilon|membrane = 11.7*8.854e-12;
  96. // c is the speed of sound in the water fluid. scaling is a scaling factor.
  97. double c = 1484, scaling = 1e10;
  98. // An electrostatic formulation is used for the electric problem.
  99. // An elastoacoustic formulation is used for the fluid-mechanic problem.
  100. formulation electrostatics, elastoacoustic;
  101. // Weak electrostatic formulation brought back to the undeformed configuration.
  102. // Since this is a nonlinear multiharmonic formulation we add an integer argument.
  103. // This means an FFT will be used to get the 20 first harmonics in the nonlinear term.
  104. electrostatics += integral(electricdomain, 20, epsilon*transpose(invjac*grad(dof(v)))*invjac*grad(tf(v)) * detjac );
  105. // The linear elasticity formulation is classical and thus predefined:
  106. elastoacoustic += integral(solid, predefinedelasticity(dof(u), tf(u), E, nu, "planestrain"));
  107. // Add the inertia terms:
  108. elastoacoustic += integral(solid, -rho*dtdt(dof(u))*tf(u));
  109. // Electrostatic forces, computed on the elements of the whole electric domain
  110. // but with mechanical deflection test functions tf(u) only on solid.
  111. //
  112. // The electrostatic forces often appear in MEMS simulations and are thus predefined.
  113. // The inputs are the gradient of the test function of u defined on the mechanical domain,
  114. // the gradient of the previously computed electric potential field and the electric permittivity.
  115. //
  116. // The electrostatic forces must be computed on the mesh deformed by field umesh.
  117. // The calculations are brought back to the undeformed configuration.
  118. expression gradtfu = invjac*expression({{grad(compx(tf(u,solid)))}, {grad(compy(tf(u,solid)))}});
  119. elastoacoustic += integral(electricdomain, 20, predefinedelectrostaticforce({gradtfu.getcolumn(0),gradtfu.getcolumn(1)}, invjac*grad(v), epsilon) * detjac );
  120. // The wave equation is solved in the fluid:
  121. elastoacoustic += integral(fluid, -grad(dof(p))*grad(tf(p)) -1/pow(c,2)*dtdt(dof(p))*tf(p));
  122. // A Sommerfeld condition is used on the fluid boundary to have outgoing waves:
  123. elastoacoustic += integral(fluidboundary, -1/c*dt(dof(p))*tf(p));
  124. // Elastoacoustic coupling terms.
  125. // The first term is the forces applied by the fluid on the membrane top.
  126. // The second term is Newton's law: a membrane acceleration creates a
  127. // pressure gradient in the fluid.
  128. //
  129. // To have a good matrix conditionning the pressure source is divided by
  130. // the scaling factor and to compensate it multiplies the pressure force.
  131. // This leads to the correct membrane deflection but a pressure field divided by the scaling factor.
  132. elastoacoustic += integral(membranetop, -dof(p)*tf(compy(u)) * scaling);
  133. elastoacoustic += integral(membranetop, rho*dtdt(dof(compy(u)))*tf(p) / scaling);
  134. // Solve the Laplace equation in the vacuum gap to smoothly deform the mesh.
  135. // umesh is forced to field u on region solid:
  136. umesh.setconstraint(solid, u);
  137. formulation laplacian;
  138. laplacian += integral(vacuumgap, grad(dof(compx(umesh)))*grad(tf(compx(umesh))) + grad(dof(compy(umesh)))*grad(tf(compy(umesh))) );
  139. // NONLINEAR ITERATION TO GET THE DEFLECTION:
  140. // Start with an all-zero solution vector for the elastoacoustic formulation:
  141. vec solup(elastoacoustic);
  142. double relresnorm = 1; int iter = 0;
  143. while (relresnorm > 1e-5)
  144. {
  145. electrostatics.generate();
  146. vec solv = solve(electrostatics.A(), electrostatics.b());
  147. // Transfer the data from the solution vector to the v field:
  148. v.setdata(electricdomain, solv);
  149. // Use the now known electric potential v to compute the membrane deflection:
  150. elastoacoustic.generate();
  151. vec b = elastoacoustic.b();
  152. mat A = elastoacoustic.A();
  153. // Compute the norm of the relative residual:
  154. relresnorm = (b-A*solup).norm()/b.norm();
  155. solup = solve(A,b);
  156. u.setdata(solid, solup);
  157. p.setdata(fluid, solup);
  158. // Smooth the mesh on the vacuum gap:
  159. laplacian.generate();
  160. vec solumesh = solve(laplacian.A(), laplacian.b());
  161. umesh.setdata(vacuumgap, solumesh);
  162. // Also save the u field on region solid to umesh.
  163. // This is done by selecting field u with |u on the solu vector.
  164. umesh.setdata(solid, solup|u);
  165. // Print the iteration number and relative residual norm:
  166. std::cout << "Relative residual norm @iteration " << iter << " is " << relresnorm << std::endl;
  167. iter++;
  168. }
  169. // Write the electric field with an order 1 interpolation at 50 timesteps.
  170. (-grad(v)).write(electricdomain, "E.pos", 1, 50);
  171. // Write the deflection u with an order 3 interpolation:
  172. u.write(solid, "u.pos", 3);
  173. // Also write it at 50 timesteps for a time view.
  174. u.write(solid, "u.pos", 3, 50);
  175. // Do the same with p (remember to multiply it by the scaling factor to get the actual pressure):
  176. (scaling*p).write(fluid, "p.pos", 3, 50);
  177. // Code validation line. Can be removed.
  178. std::cout << (solup.norm() < 0.0002539 && solup.norm() > 0.0002537);
  179. }
  180. int main(void)
  181. {
  182. SlepcInitialize(0,{},0,0);
  183. sparselizard();
  184. SlepcFinalize();
  185. return 0;
  186. }