main.cpp 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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. u.setorder(membrane, 3);
  66. u.setorder(vacuumgap, 3);
  67. u.setorder(pillars, 2);
  68. u.setorder(insulator, 1);
  69. p.setorder(fluid, 2);
  70. v.setorder(electricdomain, 1);
  71. umesh.setorder(solid, 1);
  72. umesh.setorder(electricdomain, 1);
  73. // Clamp and ground all harmonics (i.e. 0 valued-Dirichlet conditions for u and v):
  74. u.setconstraint(clamp);
  75. v.setconstraint(ground);
  76. // Force the electric potential on the electrode to 250*sin(2pi*f0*t).
  77. // First set all v harmonics to 0 then set harmonic 2 to 250:
  78. v.setconstraint(electrode);
  79. v.harmonic(2).setconstraint(electrode, 250);
  80. // E is Young's modulus. nu is Poisson's ratio. rho is the volumic mass.
  81. // epsilon is the electric permittivity.
  82. //
  83. // The membrane is polysilicon, the insulator is silicon dioxyde.
  84. parameter E, nu, rho, epsilon;
  85. E|insulator = 70e9;
  86. E|pillars = 150e9;
  87. E|membrane = 150e9;
  88. nu|insulator = 0.17;
  89. nu|pillars = 0.3;
  90. nu|membrane = 0.3;
  91. rho|insulator = 2200;
  92. rho|pillars = 2330;
  93. rho|membrane = 2330;
  94. rho|fluid = 1000;
  95. epsilon|vacuumgap = 8.854e-12;
  96. epsilon|insulator = 3.9*8.854e-12;
  97. epsilon|pillars = 11.7*8.854e-12;
  98. epsilon|membrane = 11.7*8.854e-12;
  99. // c is the speed of sound in the water fluid. scaling is a scaling factor.
  100. double c = 1484, scaling = 1e10;
  101. // An electrostatic formulation is used for the electric problem.
  102. // An elastoacoustic formulation is used for the fluid-mechanic problem.
  103. formulation electrostatics, elastoacoustic;
  104. // Weak electrostatic formulation brought back to the undeformed configuration.
  105. // Since this is a nonlinear multiharmonic formulation we add an integer argument.
  106. // This means an FFT will be used to get the 20 first harmonics in the nonlinear term.
  107. electrostatics += integral(electricdomain, 20, epsilon*transpose(invjac*grad(dof(v)))*invjac*grad(tf(v)) * detjac );
  108. // The linear elasticity formulation is classical and thus predefined:
  109. elastoacoustic += integral(solid, predefinedelasticity(dof(u), tf(u), E, nu, "planestrain"));
  110. // Add the inertia terms:
  111. elastoacoustic += integral(solid, -rho*dtdt(dof(u))*tf(u));
  112. // Electrostatic forces, computed on the elements of the whole electric domain
  113. // but with mechanical deflection test functions tf(u) only on solid.
  114. //
  115. // The electrostatic forces often appear in MEMS simulations and are thus predefined.
  116. // The inputs are the gradient of the test function of u defined on the mechanical domain,
  117. // the gradient of the previously computed electric potential field and the electric permittivity.
  118. //
  119. // The electrostatic forces must be computed on the mesh deformed by field umesh.
  120. // The calculations are brought back to the undeformed configuration.
  121. expression gradtfu = invjac*expression({{grad(compx(tf(u,solid)))}, {grad(compy(tf(u,solid)))}});
  122. elastoacoustic += integral(electricdomain, 20, predefinedelectrostaticforce({gradtfu.getcolumn(0),gradtfu.getcolumn(1)}, invjac*grad(v), epsilon) * detjac );
  123. // The wave equation is solved in the fluid:
  124. elastoacoustic += integral(fluid, -grad(dof(p))*grad(tf(p)) -1/pow(c,2)*dtdt(dof(p))*tf(p));
  125. // A Sommerfeld condition is used on the fluid boundary to have outgoing waves:
  126. elastoacoustic += integral(fluidboundary, -1/c*dt(dof(p))*tf(p));
  127. // Elastoacoustic coupling terms.
  128. // The first term is the forces applied by the fluid on the membrane top.
  129. // The second term is Newton's law: a membrane acceleration creates a
  130. // pressure gradient in the fluid.
  131. //
  132. // To have a good matrix conditionning the pressure source is divided by
  133. // the scaling factor and to compensate it multiplies the pressure force.
  134. // This leads to the correct membrane deflection but a pressure field divided by the scaling factor.
  135. elastoacoustic += integral(membranetop, -dof(p)*tf(compy(u)) * scaling);
  136. elastoacoustic += integral(membranetop, rho*dtdt(dof(compy(u)))*tf(p) / scaling);
  137. // Solve the Laplace equation in the vacuum gap to smoothly deform the mesh.
  138. // umesh is forced to field u on region solid:
  139. umesh.setconstraint(solid, u);
  140. formulation laplacian;
  141. laplacian += integral(vacuumgap, grad(dof(compx(umesh)))*grad(tf(compx(umesh))) + grad(dof(compy(umesh)))*grad(tf(compy(umesh))) );
  142. // NONLINEAR ITERATION TO GET THE DEFLECTION:
  143. // Start with an all-zero solution vector for the elastoacoustic formulation:
  144. vec solup(elastoacoustic);
  145. double relresnorm = 1; int iter = 0;
  146. while (relresnorm > 1e-5)
  147. {
  148. electrostatics.generate();
  149. vec solv = solve(electrostatics.A(), electrostatics.b());
  150. // Transfer the data from the solution vector to the v field:
  151. v.setdata(electricdomain, solv);
  152. // Use the now known electric potential v to compute the membrane deflection:
  153. elastoacoustic.generate();
  154. vec b = elastoacoustic.b();
  155. mat A = elastoacoustic.A();
  156. // Compute the norm of the relative residual:
  157. relresnorm = (b-A*solup).norm()/b.norm();
  158. solup = solve(A,b);
  159. u.setdata(solid, solup);
  160. p.setdata(fluid, solup);
  161. // Smooth the mesh on the vacuum gap:
  162. laplacian.generate();
  163. vec solumesh = solve(laplacian.A(), laplacian.b());
  164. umesh.setdata(vacuumgap, solumesh);
  165. // Also save the u field on region solid to umesh.
  166. // This is done by selecting field u with |u on the solu vector.
  167. umesh.setdata(solid, solup|u);
  168. // Print the iteration number and relative residual norm:
  169. std::cout << "Relative residual norm @iteration " << iter << " is " << relresnorm << std::endl;
  170. iter++;
  171. }
  172. // Write the electric field with an order 1 interpolation at 50 timesteps.
  173. (-grad(v)).write(electricdomain, "E.pos", 1, 50);
  174. // Write the deflection u with an order 3 interpolation:
  175. u.write(solid, "u.pos", 3);
  176. // Also write it at 50 timesteps for a time view.
  177. u.write(solid, "u.pos", 3, 50);
  178. // Do the same with p (remember to multiply it by the scaling factor to get the actual pressure):
  179. (scaling*p).write(fluid, "p.pos", 3, 50);
  180. // Code validation line. Can be removed.
  181. std::cout << (solup.norm() < 0.000252599 && solup.norm() > 0.000252595);
  182. }
  183. int main(void)
  184. {
  185. SlepcInitialize(0,{},0,0);
  186. sparselizard();
  187. SlepcFinalize();
  188. return 0;
  189. }