5. Antre’acte: Visualization

The .ppm file format

It would be nice to view our future solutions, so we can inspect its structure which helps with interpreting results. This goes beyond just the apparent beauty of flow visualizations, as so well explained by Ascombe. It may be tempting to offload the visualization burden to a post-processing step in your favorite Matplotlib-enabled language. However, it is actually not that hard to output images in the simple .ppm file format. This was also be used for the movie in the opening of Chapter 0. A code that is compatible with our previous works is implemented below,

// Colorbar used values that ramp up and down between 0 and 255
#define RAMPUP (255*(1 - (avg - val(s, 0, 0))/(maxv - avg)))
#define RAMPDOWN (255*(1 + (avg - val(s, 0, 0))/(maxv - avg)))

// Min and Max operators for clipping
#define min(a, b) (a < b ? a : b)
#define max(a, b) (a > b ? a : b)

void output_ppm (double s[N][N], char * fname, double minv, double maxv) {
  // central value (white)
  double avg = (minv + maxv)/ 2.;
  // Open file
  FILE * fp = fopen (fname, "w");
  // Print ascii header for PPM file
  fprintf (fp, "P6 %d %d 255\n", N, N);
  // Upside up left-to-right iterator
  for (int _j = N - 1; _j >= 0; _j--)
    for (int _i = 0; _i < N; _i++) {
      // rgb value for a simple blue-white-red colorbar
      unsigned char rgb[3] = {
    val(s, 0, 0) < avg ? max(0, RAMPUP): 255,               // red
    val(s, 0, 0) < avg ? max(0, RAMPUP) : max(0, RAMPDOWN), // green
    val(s, 0, 0) < avg ? 255 : max(0, RAMPDOWN)};           // blue
      // write binary
      fwrite (rgb, sizeof(char), 3, fp);
  //close file
  fclose (fp);

I think it maybe instructive if you wrote this code yourself, copying the above by reading and rewriting line by line. But if you do not care about the .ppm format and want to copy-paste code, this bit has perhaps the least to do with the working of our solver. In any case, if we save these approx. 30 lines of codes to a file called visual.h we can test it with test_visual.c

#include "common.h"
#include "visual.h"

double s[N][N];

int main() {
  L0 = 10;
  X0 = Y0 = -L0/2;
    val (s, 0, 0) = exp(-sq(x + 2) - sq(y + 2)) - exp(-sq(x - 1) - sq(y - 2));
  output_ppm (s, "s.ppm", -.7, .7);

In the test, two Gaussian blobs are initialized, one with positive values for s in the bottom left, and the other with negative values for s near the top. One may view the s.ppm file with most image viewer software. For display on the website it needs to be converted to the more sensible .png format, which can be done using the convert command which is part of the imagemagick package. In the terminal:

$ sudo apt install imagemagick
$ gcc -DN=200 test_visual.c -lm
$ ./a.out
$ convert s.ppm s.png
The resulting image shows the Gaussian blobs

Movie maker

It is also possible to make a movie, first we need to generate many images, called s-0XXX.ppm, where XXX indicates the frame number.

#include "common.h"
#include "visual.h"

double s[N][N];

int main() {
  L0 = 10;
  X0 = Y0 = -L0/2;
  int frame = 0;
  // Loop over displacement (dx)
  for (double dx = -1; dx < 1; dx += 0.02) {
    val (s, 0, 0) = exp(-sq(x - dx + 2) - sq(y + 2)) - exp(-sq(x - dx - 1) - sq(y - 2));
      char fname[99];
      sprintf (fname, "s-%04d.ppm", frame);
      output_ppm (s, fname, -.7, .7);
      frame ++;

The %04d format specifier is used for so-called zero padding. This padding adds leading zeros so that all printed number will have four digits. This is useful to determine that frame 10 comes somewhere after frame 2, which is not what we would get with regular alpha-numeric sorting. We can generate a movie from these frames, again using convert in the terminal,

$ gcc -DN=200 test_visual.c -lm
$ rm *.ppm
$ ./a.out
$ convert s-*.ppm s.mp4

the rm *.ppm command removes any precious files with the .ppm extension in the current folder. Later, we can then use convert to append all frames (from files whose names start with “s” and ending with “.ppm”) into a movie called “s.mp4”. The result is shown below.

It is not unlikely that you run into issues whilst viewing the generated movie. This is because convert is not really designed to handle movies well. If so, an alternative is to use FFmpeg, which needs some extra instructions to understand the input image sequence.

$ sudo apt install ffmpeg
$ ffmpeg -pattern_type glob -i 's-*.ppm' s.mp4

It is less intuitive, but much more powerful. For a web-friendly encoding i use,

ffmpeg -pattern_type glob -i 's-*.ppm' -c:v libx264 -vf format=yuv420p s.mp4

We will use this visualization to test our tracer_advection scheme in Chapt. 6

The marvelous design of this website is taken from Suckless.org