// This code simulates the steady-state vibration of a 2D CMUT (capacitive micromachined
// ultrasonic transducer). A CMUT is a micromembrane, actuated with an electrostatic 
// force, that vibrates in a fluid (water here). Due to the small dimensions at play
// the electric excitation frequency is in the megahertz range.
// 
// In this code the electric excitation is a sine wave so that the vibration is
// periodic. Due to the electroelastic nonlinearity however the membrane vibration
// and the pressure field includes new harmonics. These harmonics are computed 
// with the multiharmonic resolution method in which all calculations are brought 
// back to the undeformed configuration as detailed in the paper
// "Steady-State, Nonlinear Analysis of Large Arrays of Electrically Actuated 
// Micromembranes Vibrating in a Fluid" (A Halbach, C Geuzaine).


#include "sparselizardbase.h"


using namespace mathop;

void sparselizard(void)
{	
    // The domain regions as defined in 'cmut2d.geo':
    int insulator = 1, pillars = 2, vacuumgap = 3, membrane = 4, fluid = 5, ground = 6, clamp = 6, electrode = 7, fluidboundary = 8;
    
    // The mesh can be curved!
    mesh mymesh("cmut2d.msh");
    
    // Define the region for the mechanical problem:
    int solid = regionunion({insulator, pillars, membrane});
    // Define the region for the electric problem:
    int electricdomain = regionunion({insulator, pillars, membrane, vacuumgap});
    // Define the membrane top line:
    int membranetop = regionintersection({membrane, fluid});
    
    // Nodal shape functions 'h1' for the electric potential field v, acoustic pressure
    // field p and membrane deflection u (u has components x and y).
    // umesh smoothly deforms the mesh in the vacuum gap for a given membrane deflection.
    //
    // Multiharmonic fields are used. This means that the time-dependency of a field h 
    // is approximated by a truncated Fourier series:
    //
    // h = h1 + h2*sin(2pi*f0*t) + h3*cos(2pi*f0*t) + h4*sin(2 * 2pi*f0*t) + h5*cos(2 * 2pi*f0*t) + ... 
    //
    // where f0 is the fundamental frequency, t is the time variable and hi are the Fourier coefficients.
    // The second argument in the field declaration lists all harmonics considered in the truncation.
    // For example the displacement field u is supposed to be well approximated by 
    // u = u1 + u4*sin(2 * 2pi*f0*t) + u5*cos(2 * 2pi*f0*t) while in the pressure field the constant is removed.
    //
    field u("h1xy",{1,4,5}), umesh("h1xy",{1,4,5}), v("h1",{2,3}), p("h1",{4,5});
    
    // Set the fundamental frequency f0 to 6 MHz:
    setfundamentalfrequency(6e6);
    
    // Since this is a multiharmonic resolution on a mesh deformed by a multiharmonic 
    // displacement field the calculations must be brought back on the undeformed 
    // configuration with a variable change [x y z]deformed = [x y z]undeformed + umesh. 
    //
    // This is the Jacobian matrix for the change of variable:
    //
    expression jac = array2x2(1+dx(compx(umesh)),dx(compy(umesh)),  dy(compx(umesh)),1+dy(compy(umesh)));
    expression invjac = inverse(jac);
    expression detjac = determinant(jac);
    // The expressions above might appear multiple times in a formulation 
    // and reusing them while generating a formulation will give a speedup:
    invjac.reuseit(); detjac.reuseit();

    // Use interpolation order:
    //
    // - 3 for u on the membrane
    // - 2 for u on the pillars
    // - 1 elsewhere
    //
    // - 2 for the pressure field p
    //
    // - 1 for the electric potential v
    //
    // Default order is 1.
    
    u.setorder(pillars, 2);
    u.setorder(membrane, 3);
    p.setorder(fluid, 2);
    
    // Clamp and ground all harmonics (i.e. 0 valued-Dirichlet conditions for u and v):
    u.setconstraint(clamp);
    v.setconstraint(ground);
    // Force the electric potential on the electrode to 250*sin(2pi*f0*t).
    // First set all v harmonics to 0 then set harmonic 2 to 250:
    v.setconstraint(electrode);
    v.harmonic(2).setconstraint(electrode, 250);
  
    // E is Young's modulus. nu is Poisson's ratio. rho is the volumic mass.
    // epsilon is the electric permittivity. 
    //
    // The membrane is polysilicon, the insulator is silicon dioxyde.
    
    parameter E, nu, rho, epsilon;
    
    E|insulator =		70e9;
    E|pillars =			150e9;
    E|membrane =		150e9;
    
    nu|insulator =		0.17;
    nu|pillars =		0.3;
    nu|membrane =		0.3;
    
    rho|insulator =		2200;
    rho|pillars =		2330;
    rho|membrane =		2330;
    rho|fluid = 		1000;
    
    epsilon|vacuumgap =	8.854e-12;
    epsilon|insulator =	3.9*8.854e-12;
    epsilon|pillars =	11.7*8.854e-12;
    epsilon|membrane =	11.7*8.854e-12;
    
    // c is the speed of sound in the water fluid. scaling is a scaling factor.
    double c = 1484, scaling = 1e10;
    
    // An electrostatic formulation is used for the electric problem.
    // An elastoacoustic formulation is used for the fluid-mechanic problem.
    formulation electrostatics, elastoacoustic;
	
    // Weak electrostatic formulation brought back to the undeformed configuration.
    // Since this is a nonlinear multiharmonic formulation we add an integer argument.
    // This means an FFT will be used to get the 20 first harmonics in the nonlinear term.
    electrostatics += integral(electricdomain, 20, epsilon*transpose(invjac*grad(dof(v)))*invjac*grad(tf(v)) * detjac );
    
    // The linear elasticity formulation is classical and thus predefined:
    elastoacoustic += integral(solid, predefinedelasticity(dof(u), tf(u), E, nu, "planestrain"));
    // Add the inertia terms:
    elastoacoustic += integral(solid, -rho*dtdt(dof(u))*tf(u));
    
    // Electrostatic forces, computed on the elements of the whole electric domain
    // but with mechanical deflection test functions tf(u) only on solid.
    // 
    // The electrostatic forces often appear in MEMS simulations and are thus predefined.
    // The inputs are the gradient of the test function of u defined on the mechanical domain,
    // the gradient of the previously computed electric potential field and the electric permittivity.
    //
    // The electrostatic forces must be computed on the mesh deformed by field umesh.
    // The calculations are brought back to the undeformed configuration.
    expression gradtfu = invjac*expression({{grad(compx(tf(u,solid)))}, {grad(compy(tf(u,solid)))}});
    elastoacoustic += integral(electricdomain, 20, predefinedelectrostaticforce({gradtfu.getcolumn(0),gradtfu.getcolumn(1)}, invjac*grad(v), epsilon) * detjac );
    
    // The wave equation is solved in the fluid:
    elastoacoustic += integral(fluid, -grad(dof(p))*grad(tf(p)) -1/pow(c,2)*dtdt(dof(p))*tf(p));
    // A Sommerfeld condition is used on the fluid boundary to have outgoing waves:
    elastoacoustic += integral(fluidboundary, -1/c*dt(dof(p))*tf(p));
    
    // Elastoacoustic coupling terms.
    // The first term is the forces applied by the fluid on the membrane top.
    // The second term is Newton's law: a membrane acceleration creates a 
    // pressure gradient in the fluid.
    //
    // To have a good matrix conditionning the pressure source is divided by 
    // the scaling factor and to compensate it multiplies the pressure force.
    // This leads to the correct membrane deflection but a pressure field divided by the scaling factor.
    elastoacoustic += integral(membranetop, -dof(p)*tf(compy(u)) * scaling);
    elastoacoustic += integral(membranetop, rho*dtdt(dof(compy(u)))*tf(p) / scaling);
    
    
    // Solve the Laplace equation in the vacuum gap to smoothly deform the mesh.
    // umesh is forced to field u on region solid:
    umesh.setconstraint(solid, u);

    formulation laplacian;
    laplacian += integral(vacuumgap, grad(dof(compx(umesh)))*grad(tf(compx(umesh))) + grad(dof(compy(umesh)))*grad(tf(compy(umesh))) );
    
    
    // NONLINEAR ITERATION TO GET THE DEFLECTION:
  	
    // Start with an all-zero solution vector for the elastoacoustic formulation:
    vec solup(elastoacoustic);
    
    double relresnorm = 1; int iter = 0; 
    while (relresnorm > 1e-5)
    {
        electrostatics.generate();
        vec solv = solve(electrostatics.A(), electrostatics.b());
        // Transfer the data from the solution vector to the v field:
        v.setdata(electricdomain, solv);
        
        // Use the now known electric potential v to compute the membrane deflection:
        elastoacoustic.generate();
        
        vec b = elastoacoustic.b();
        mat A = elastoacoustic.A();
        // Compute the norm of the relative residual:
        relresnorm = (b-A*solup).norm()/b.norm();

        solup = solve(A,b);
        u.setdata(solid, solup);
        p.setdata(fluid, solup);
        
        // Smooth the mesh on the vacuum gap:
        laplacian.generate();
        vec solumesh = solve(laplacian.A(), laplacian.b());
        umesh.setdata(vacuumgap, solumesh);

        // Also save the u field on region solid to umesh.
        // This is done by selecting field u with |u on the solu vector.
        umesh.setdata(solid, solup|u);
        
        // Print the iteration number and relative residual norm:
        std::cout << "Relative residual norm @iteration " << iter << " is " << relresnorm << std::endl;
        iter++;
    }
    
    // Write the electric field with an order 1 interpolation at 50 timesteps.
    (-grad(v)).write(electricdomain, "E.pos", 1, 50);
    
    // Write the deflection u with an order 3 interpolation:
    u.write(solid, "u.pos", 3); 
    // Also write it at 50 timesteps for a time view.
    u.write(solid, "u.pos", 3, 50);
    // Do the same with p (remember to multiply it by the scaling factor to get the actual pressure):
    (scaling*p).write(fluid, "p.pos", 3, 50);
    
    // Code validation line. Can be removed.
    std::cout << (solup.norm() < 0.0002539 && solup.norm() > 0.0002537);
}

int main(void)
{	
    SlepcInitialize(0,{},0,0);

    sparselizard();

    SlepcFinalize();

    return 0;
}

