Menu

postProcess/getData.c

getData.c

Sample velocity magnitude and viscous dissipation from a bretherton.c snapshot onto a uniform grid, for rendering.

The dispersed phase is f = 1 and the continuous wetting phase is f = 0 (see simulationCases/bretherton.c), so the local viscosity is \(\mu(f) = f\mu_R + (1-f)\) in units of \(\mu_c\). This is the opposite assignment to the bursting-bubble post-processors, where the liquid carries f = 1; taking their mapping unchanged would paint the bubble interior with the continuous-phase viscosity and overstate dissipation inside it by two orders of magnitude.

The tube wall is an embedded boundary, so cells inside the solid carry no meaningful velocity. cs is emitted alongside the fields and the renderer masks on it; nothing here guesses a value for the solid.

Usage

./getData <dump> <xmin> <ymin> <xmax> <ymax> <ny> <muR>

Columns on stderr: x y cs f log10(mu D:D) |u| u_x.

u_x is the axial component. The mean of |u| inside a bubble exceeds its translation speed whenever the interior recirculates, so a plug-velocity check must use u_x, not the magnitude.

Dissipation is reported as \(\log_{10}(\mu\,\mathbf{D}\!:\!\mathbf{D})\), matching the sibling convention; the true viscous dissipation rate is \(2\mu\,\mathbf{D}\!:\!\mathbf{D}\), so this is a constant \(\log_{10}2\) below it and the colourbar is labelled accordingly.

#include <errno.h>
#include <math.h>

#include "utils.h"
#include "output.h"

scalar f[], cs[];
vector u[];
scalar D2c[], vel[], ux[];

int main (int argc, char const *argv[])
{
  if (argc < 8) {
    fprintf (ferr, "usage: %s <dump> <xmin> <ymin> <xmax> <ymax> <ny> <muR>\n",
             argv[0]);
    return 1;
  }
  char filename[4096];
  snprintf (filename, sizeof(filename), "%s", argv[1]);

atof cannot distinguish “0” from unparseable text, so a mistyped viscosity ratio would silently become zero and paint the whole field with the continuous-phase viscosity.

  double val[6];
  const char * names[6] = {"xmin", "ymin", "xmax", "ymax", "ny", "muR"};
  for (int k = 0; k < 6; k++) {
    char * end = NULL;
    errno = 0;
    val[k] = strtod (argv[k+2], &end);
    if (end == argv[k+2] || *end != '\0' || errno == ERANGE ||
        !isfinite (val[k])) {
      fprintf (ferr, "ERROR: %s is not a finite number: '%s'\n",
               names[k], argv[k+2]);
      return 1;
    }
  }
  double xmin = val[0], ymin = val[1], xmax = val[2], ymax = val[3];

ny arrives as a double, and casting a fractional or very large double to int is undefined behaviour, so bound it before the conversion rather than after.

  if (val[4] != floor (val[4]) || val[4] < 2. || val[4] > 100000.) {
    fprintf (ferr, "ERROR: ny must be a whole number in [2, 100000], got %g\n",
             val[4]);
    return 1;
  }
  int ny = (int) val[4];
  double muR = val[5];
  if (muR <= 0.) {
    fprintf (ferr, "ERROR: muR must be positive, got %g\n", muR);
    return 1;
  }

  /* ny and nx index a spacing of (max-min)/(n-1), so a single sample
     would divide by zero. */
  if (ny < 2 || xmax <= xmin || ymax <= ymin) {
    fprintf (ferr, "ERROR: need xmax>xmin, ymax>ymin and ny>=2\n");
    return 1;
  }

A missing or unreadable dump leaves the fields at their defaults, and sampling them would emit a plausible-looking grid of zeros.

  if (!restore (file = filename)) {
    fprintf (ferr, "ERROR: cannot restore snapshot '%s'\n", filename);
    return 1;
  }

Axisymmetric strain-rate invariant. D22 is the hoop term \(u_y/y\), guarded on the axis.

  foreach() {
    double D11 = (u.y[0,1] - u.y[0,-1])/(2.*Delta);

The hoop strain rate is \(u_r/r\). On the axis \(u_r \to 0\) linearly, so the limit is \(\partial u_r/\partial r\), which is D11; taking zero there would drop a real contribution to \(\mathbf{D}:\mathbf{D}\).

    double D22 = (y > 1e-10) ? u.y[]/y : D11;
    double D33 = (u.x[1,0] - u.x[-1,0])/(2.*Delta);
    double D13 = 0.5*((u.y[1,0] - u.y[-1,0] + u.x[0,1] - u.x[0,-1])/(2.*Delta));
    double D2 = sq(D11) + sq(D22) + sq(D33) + 2.*sq(D13);
    double muLocal = clamp(f[],0.,1.)*muR + (1. - clamp(f[],0.,1.));
    double diss = muLocal*D2;
    D2c[] = diss > 0. ? log(diss)/log(10.) : -10.;
    vel[] = sqrt (sq(u.x[]) + sq(u.y[]));
    ux[] = u.x[];
  }

interpolate() reads neighbours, including ghosts. Without this the ghosts keep their default zero and samples near the axis and the domain edges are pulled towards it.

  boundary ({D2c, vel, ux});

  double Deltay = (ymax - ymin)/(ny - 1);
  double nxd = (xmax - xmin)/Deltay + 1.;
  if (!(nxd >= 2. && nxd <= 100000.)) {
    fprintf (ferr, "ERROR: computed nx = %g is outside [2, 100000]; "
             "adjust the x range or ny\n", nxd);
    return 1;
  }
  int nx = (int) nxd;
  if (nx < 2) {
    fprintf (ferr, "ERROR: computed nx < 2; widen the x range or raise ny\n");
    return 1;
  }
  double Deltax = (xmax - xmin)/(nx - 1);

  for (int i = 0; i < nx; i++) {
    double xp = xmin + i*Deltax;
    for (int j = 0; j < ny; j++) {
      double yp = ymin + j*Deltay;

interpolate() returns nodata outside the domain and inside the embedded solid. Emitting that sentinel as a number would let a renderer plot 1e30 as a real value, so it becomes nan here and the renderer masks on it.

      double vcs = interpolate (cs, xp, yp);
      double vf  = interpolate (f, xp, yp);
      double vd  = interpolate (D2c, xp, yp);
      double vu  = interpolate (vel, xp, yp);
      double vax = interpolate (ux, xp, yp);
      fprintf (ferr, "%g %g %g %g %g %g %g\n", xp, yp,
               vcs > 0.5*nodata ? nan("") : vcs,
               vf  > 0.5*nodata ? nan("") : vf,
               vd  > 0.5*nodata ? nan("") : vd,
               vu  > 0.5*nodata ? nan("") : vu,
               fabs(vax) > 0.5*nodata ? nan("") : vax);
    }
  }
  return 0;
}