sandbox/tfullana/test/free-angle.c

    #define JVIEW 0
    #define ANGLE 0
    #define ADAPT 0
    #define GRID_CONV 1
    
    #include "advection.h"
    #include "contact.h"
    #include "vof.h"
    
    #define shift 0.05
    
    #include <vofi.h>
    #pragma autolink -L$HOME/local/lib -lvofi
    
    typedef vofi_creal creal;
    #define Get_fh vofi_Get_fh
    #define Get_cc vofi_Get_cc
    
    static double sphere (creal p[dimension]){
      return sq(p[0]) + sq(p[1] + shift) - sq(0.5);
    }
    
    static void vofi (scalar c, int levelmax)
    {
      double fh = Get_fh (sphere, NULL, 1./(1 << levelmax), dimension, 0);
      foreach() {
        creal p[2] = {x - Delta/2., y - Delta/2.};
        c[] = Get_cc (sphere, p, Delta, fh, dimension);
      }
    }
    
    #if JVIEW
    #include "display.h"
    #endif
    
    #define TEND 10.0
    #define c1 0.5
    #define c2 0.2
    #define tau 1.0
    
    #define radius 0.5
    
    #define theta_0 acos(shift / radius)
    
    #define f_err 1e-3
    #define u_err 1e-3
    
    int LEVEL = 8, MINLEVEL = 6, MAXLEVEL = 9;
    
    scalar f[], cf[], kappa[];
    scalar *interfaces = {f}, *tracers = NULL;
    vector h[];
    
    // Global variables for the boundary condition
    double angle_imposed_left = pi/2., angle_imposed_right = pi/2.;
    
    // We store the "measured" angle globally so we can log exactly what was used
    double angle_recorded_left = pi/2., angle_recorded_right = pi/2.;
    
    double kappa_num = 1./(0.5 );
    double time_prev = 0.;
    
    // --- Analytical Functions ---
    double analytical_position_right(double t) {
        double s = tau * sin((pi * t) / tau) / pi;
        return radius * sin(theta_0) * exp(c1 * s);
    }
    
    double analytical_angle_right(double t) {
        double s = tau * sin((pi * t) / tau) / pi;
        return pi / 2. + atan((-1. / tan(theta_0)) * exp(2. * c1 * s) + (c2 / (2. * c1) * (exp(2. * c1 * s) - 1.)));
    }
    
    double analytical_angle_left(double t) {
        double s = tau * sin((pi * t) / tau) / pi;
        return pi / 2. + atan((-1. / tan(theta_0)) * exp(2. * c1 * s) - (c2 / (2. * c1) * (exp(2. * c1 * s) - 1.)));
    }
    
    double analytical_curvature_right(double t) {
        return (1. / (radius)) * exp(3. * c1 * sin(pi * t / tau) * (tau / pi));
    }
    
    double analytical_curvature_right2(double t) {
        double s = tau * sin((pi * t) / tau) / pi;
        double k0 = (1. / (radius));
        double cot = 1. / tan(theta_0);
        return k0*pow(1.+sq(cot),3./2.)*exp(3.*c1*s)/pow((1. + sq(cot)*exp(4.*c1*s)),3./2.);
    }
    
    double k_ode_func(double t_val, double k_val) {
        double ang = analytical_angle_right(t_val);
        double a = -cos(ang);
        double b = sin(ang);
        return -3. * k_val * cos(pi * t_val / tau) * (sq(a)*c1 + a*b*c2 - sq(b)*c1);
    }
    
    double velocity_x(double t, double x, double y) {
        return c1 * cos(pi * t / tau) * x + c2 * cos(pi * t / tau) * y;
    }
    
    double velocity_y(double t, double x, double y) {
        return -c1 * cos(pi * t / tau) * y;
    }
    
    // The Rule: Applies the current global variables to the boundary
    #if ANGLE
    // h.t[bottom] = contact_angle((x < 0.) ? (angle_imposed_left) : (angle_imposed_right));
    h.t[bottom] = contact_angle((x < 0.) ? (analytical_angle_left(t)) : (analytical_angle_right(t)));
    #else
    h.t[bottom] = contact_angle((x < 0.) ? (angle_imposed_left) : (angle_imposed_right));
    #endif
    int main()
    {
        if (pid() == 0) system("mkdir -p data");
        origin(-1., 0.);
    
        f.height = h;
        f.tracers = {cf};
    
    #if GRID_CONV
        for (LEVEL = MINLEVEL; LEVEL <= MAXLEVEL; LEVEL += 1)
        {   size(2);
            init_grid(1 << LEVEL);
            
            // Reset angles to equilibrium at start of every level
            angle_imposed_left = theta_0;
            angle_imposed_right = theta_0;
            angle_recorded_left = theta_0;
            angle_recorded_right = theta_0;
            
    
            DT = 0.02*(2./pow(2, LEVEL))/(fabs(c1) + fabs(c2));
    
            time_prev = 0.;
            kappa_num = 1./(radius);
            run();
        }
    #else
        init_grid(1 << LEVEL);
        DT = 0.1/(max(c1, c2)*(1 << LEVEL));
        run();
    #endif
    }
    
    event init(t = 0)
    {
        // fraction(f, -(sq(x) + sq(y + shift) - sq(radius)));
        // foreach ()
        //     cf[] = f[];
        // boundary({f});
    	vertex scalar phi[];
    	foreach_vertex()
    		phi[] = sq(0.5) - (sq(x) + sq(y + shift));
    
    	fractions (phi, f);
    	vofi (f, LEVEL);
    	boundary({f});
    }
    
    event impose_velocity(i++)
    {
        foreach_face(x) u.x[] = velocity_x(t, x, y);
        foreach_face(y) u.y[] = velocity_y(t, x, y);
    }
    
    // --- NEW EVENT: Update Angle BEFORE VOF Solver (Removes Lag) ---
    // "stability" runs early in the timestep, before advection.
    event update_boundary (i++) {
        
        // 1. Calculate Curvature of the CURRENT shape
        curvature(f, kappa);
    
        double new_angle_left = angle_imposed_left; 
        double new_angle_right = angle_imposed_right;
    
        // 2. Extrapolate Angles (One Layer Above)
        foreach (reduction(max:new_angle_left) reduction(max:new_angle_right))
        {
            if ((y <= (2. * Delta)) & (y > (Delta)) & (h.x[] != nodata) & (f[] < (1. - 1e-6)) & (f[] > 1e-6))
            {
                if (x < 0.) {
                    coord n = interface_normal(point, f);
                    double theta_a = pi / 2. - asin(n.y);
                    double tmp = pow(((h.x[0, 1] - h.x[]) / 1.0), 2);
                    new_angle_left = theta_a + 1.5 * Delta * kappa[] * sqrt(1 + tmp) / sin(theta_a);
                }
                if (x > 0.) {
                    coord n = interface_normal(point, f);
                    double theta_a = pi / 2. - asin(n.y);
                    double tmp = pow(((h.x[0, 1] - h.x[]) / 1.0), 2);
                    new_angle_right = theta_a + 1.5 * Delta * kappa[] * sqrt(1 + tmp) / sin(theta_a);
                }
            }
        }
    
        // 3. Update Global Variables IMMEDIATELY
        // The VOF solver will run *after* this and see these new values.
    #if !ANGLE
        angle_imposed_left = new_angle_left;
        angle_imposed_right = new_angle_right;
    #endif
        
        // Store for logging later
        angle_recorded_left = new_angle_left;
        angle_recorded_right = new_angle_right;
    }
    
    
    // --- LOGGING EVENT (Runs at end of step) ---
    event log_results(i++; t <= TEND)
    {
        char name[80], name2[80], name3[80], name4[80], name5[80];
    #if GRID_CONV
        sprintf(name, "data/positions_c1_%g_c2_%g_shift%g_ANGLE%d_LEVEL%d", c1, c2, shift, ANGLE, LEVEL);
        sprintf(name2, "data/angles_left_c1_%g_c2_%g_shift%g_ANGLE%d_LEVEL%d", c1, c2, shift, ANGLE, LEVEL);
        sprintf(name3, "data/angles_right_c1_%g_c2_%g_shift%g_ANGLE%d_LEVEL%d", c1, c2, shift, ANGLE, LEVEL);
        sprintf(name4, "data/curvatures_c1_%g_c2_%g_shift%g_ANGLE%d_LEVEL%d", c1, c2, shift, ANGLE, LEVEL);
        sprintf(name5, "data/errors_c1_%g_c2_%g_shift%g_ANGLE%d_LEVEL%d", c1, c2, shift, ANGLE, LEVEL);
    #else
        sprintf(name, "data/positions");
        sprintf(name2, "data/angles_left");
        sprintf(name3, "data/angles_right");
        sprintf(name4, "data/curvatures");
        sprintf(name5, "data/errors");
    #endif
    
        // We only calculate position/curvature here for Reporting.
        // The angle used was already calculated in 'update_boundary'.
        
        double position_left = 0., position_right = 0., position_ref_right = analytical_position_right(t);
        double curvature_left = 0., curvature_right = 0., curvature_ref_right = analytical_curvature_right2(t);
        double angle_ref_right = analytical_angle_right(t);
    
    #if ADAPT
        adapt_wavelet({f, u}, (double[]){f_err, u_err, u_err, u_err}, LEVEL);
    #endif
    
        // Re-calculate curvature of the RESULTING shape for logging
        curvature(f, kappa);
    
        foreach_boundary(bottom,
            reduction(min:position_left) reduction(max:position_right)
            reduction(max:curvature_left) reduction(max:curvature_right))
        {
            if ((h.x[] != nodata) & (f[] < (1. - 1e-6)) & (f[] > (1e-6))) {
                if (x < 0.) {
                    position_left = x + Delta * height(h.x[]);
                    curvature_left = kappa[];
                }
                if (x > 0.) {
                    position_right = x + Delta * height(h.x[]);
                    curvature_right = kappa[];
                }
            }
        }
    
        // RK4 Reference Update
        double my_dt = t - time_prev;
        if (my_dt > 0.0) {
            double k1 = k_ode_func(time_prev, kappa_num);
            double k2 = k_ode_func(time_prev + 0.5*my_dt, kappa_num + 0.5*my_dt*k1);
            double k3 = k_ode_func(time_prev + 0.5*my_dt, kappa_num + 0.5*my_dt*k2);
            double k4 = k_ode_func(time_prev + my_dt, kappa_num + my_dt*k3);
            kappa_num += (my_dt / 6.0) * (k1 + 2.*k2 + 2.*k3 + k4);
        }
        time_prev = t;
    
        if (pid() == 0) {
            static FILE *fp = fopen(name, "w");
            static FILE *fp2 = fopen(name2, "w");
            static FILE *fp3 = fopen(name3, "w");
            static FILE *fp4 = fopen(name4, "w");
            static FILE *fp5 = fopen(name5, "w");
    
            double position_err_right = fabs(position_right - position_ref_right)/fabs(position_ref_right);
            double angle_err_right = fabs(angle_recorded_right - angle_ref_right)/fabs(angle_ref_right);
            double curvature_err_right = fabs(curvature_right - curvature_ref_right)/fabs(curvature_ref_right);
    
            fprintf(fp, "%lf %lf %lf %lf\n", t, position_left, position_right, position_ref_right);
            // We log 'angle_recorded_...' which is the value we calculated at start of step
            fprintf(fp2, "%lf %lf %lf %lf\n", t, 0., angle_recorded_left, 0.); 
            fprintf(fp3, "%lf %lf %lf %lf %lf\n", t, 0., angle_recorded_right, 0., angle_ref_right);
            fprintf(fp4, "%lf %lf %lf %lf %lf %lf\n", t, curvature_left, curvature_right, curvature_ref_right, kappa_num, my_dt);
            fprintf(fp5, "%lf %lf %lf %lf\n", t, position_err_right, angle_err_right, curvature_err_right);
    
            fflush(fp); fflush(fp2); fflush(fp3); fflush(fp4); fflush(fp5);
        }
    }