Skip to content

Bulk viscosity, live

The extra pressure a fluid feels while it is compressed or expanded, Stokes's bulk viscous term Π = −ζ div(v), ζ the bulk viscosity and div(v) the rate the fluid's volume grows per volume. A compressing fluid pushes back harder than its equilibrium pressure and an expanding one less, so the work done against it always goes to heat. Below, the divergence is swept from a compression of 50 per second to an expansion of 50 per second, one per second a step, twenty steps a second, and at each the library's law (lusine.laws.bulk_viscosity) gives the extra pressure on your GPU as you read.

This page runs the program on your GPU through WebGPU, so it needs a browser with WebGPU (a recent Chrome, Edge, Safari or Firefox). Without it the program does not run and nothing is drawn below.

What it shows

  • The strip above the controls is green while every check holds, and turns red for good once one fails.
  • The law: the first chart draws the extra pressure against the divergence, a point a step over one whole sweep, and beside it the extra pressure at the opposite divergence, negated: the two lie one over the other. The second draws the heating, the work done against the extra pressure per volume and time, −Π div(v): a parabola that touches zero only where the fluid neither grows nor shrinks. Below them, the divergence, the extra pressure, the extra pressure with twice the viscosity, and the heating.
  • The checks, each held at every step of the sweep: never cools, the heating never below zero; odd, the opposite divergence giving exactly the opposite pressure, bit for bit; linear, twice the viscosity giving exactly twice the pressure; Stokes's hypothesis, a fluid of no bulk viscosity (a monatomic gas's) feeling no extra pressure however fast it is compressed.

Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The bulk viscosity (2 Pa s) and the sweep are the program's settings, which the host may write; this page offers no control to change them yet.

The program

The sweep, the law's answers and the checks, the panels the page draws, and the one procedure, main, run each step: the sweep moves on, the law is asked at the divergence and its variants, and the checks are taken.

// The bulk viscosity law's live page: a fluid's rate of expansion swept from a fast compression to a fast expansion,
// the library's extra pressure at each, with the heating it does, beside the same law at the opposite rate and with
// twice the viscosity, with the page's checks computed here on the GPU and shown by the panels the program declares.

namespace bulk_viscosity;

lusine.version version = { { number = 1 } };

// The reader's settings: the fluid's bulk viscosity, and the sweep: the divergence it starts at and how far each step
// moves it.
host struct {
  float<Pa*s> viscosity = 2.0 Pa*s; float<s^-1> lowest = -50.0 s^-1; float<s^-1> stride = 1.0 s^-1;
} settings;

// The sweep: the step it is at, from 0 to 100 and round again, starting from the middle, and the velocity's
// divergence there, the rate the fluid's volume grows per volume.
struct { integer step = 49; float<s^-1> divergence; } sweep;

// The extra pressure at the divergence; the same law at the opposite divergence, negated; with twice the viscosity;
// and the rate the work done against the extra pressure heats the fluid, -Pi div(v), per volume.
struct { float<Pa> pressure; float<Pa> mirrored; float<Pa> doubled; float<W/m3> heating; } law;

// The checks: the heating never negative, the fluid never cooled; the opposite divergence giving exactly the opposite
// pressure; twice the viscosity exactly twice the pressure; and with no bulk viscosity (Stokes's hypothesis, a
// monatomic gas's) no extra pressure at any divergence.
struct { bool heats = true; bool odd = true; bool linear = true; bool stokes = true; } checks;

// A strip of lamps the page draws, lit green while every check holds and red once one fails.
struct lamp { vector<float, 4> colour; };
lattice<lamp, 2> lamps(spacing = 1 m, boundary = wall);

vector<float, 4> lamp_colour(bool holds)
{
  return holds ? vector<float, 4>(0.15, 0.6, 0.3, 1.0) : vector<float, 4>(0.75, 0.15, 0.15, 1.0);
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the law", chart = "extra pressure", quantity = law.pressure, steps = 101,
                    against = sweep.divergence, label = "Pi" } },
  lusine.series { { panel = "the law", chart = "extra pressure", quantity = law.mirrored, steps = 101,
                    against = sweep.divergence, label = "mirrored, negated" } },
  lusine.series { { panel = "the law", chart = "heating", quantity = law.heating, steps = 101,
                    against = sweep.divergence, label = "-Pi div(v)" } },
  lusine.value { { panel = "the law", quantity = sweep.divergence, label = "divergence" } },
  lusine.value { { panel = "the law", quantity = law.pressure, label = "extra pressure" } },
  lusine.value { { panel = "the law", quantity = law.doubled, label = "with twice the viscosity" } },
  lusine.value { { panel = "the law", quantity = law.heating, label = "heating" } },
  lusine.value { { panel = "the checks", quantity = checks.heats, label = "never cools" } },
  lusine.value { { panel = "the checks", quantity = checks.odd, label = "odd" } },
  lusine.value { { panel = "the checks", quantity = checks.linear, label = "linear" } },
  lusine.value { { panel = "the checks", quantity = checks.stokes, label = "Stokes's hypothesis" } },
};

void main()
{
  sweep.step = sweep.step >= 100 ? 0 : sweep.step + 1;
  sweep.divergence = settings.lowest + float(sweep.step) * settings.stride;
  law.pressure = lusine.laws.bulk_viscosity(settings.viscosity, sweep.divergence);
  law.mirrored = -lusine.laws.bulk_viscosity(settings.viscosity, divergence = -sweep.divergence);
  law.doubled = lusine.laws.bulk_viscosity(viscosity = 2.0 * settings.viscosity, sweep.divergence);
  law.heating = -law.pressure * sweep.divergence;
  checks.heats = checks.heats && law.heating >= 0.0 W/m3;
  checks.odd = checks.odd && law.mirrored == law.pressure;
  checks.linear = checks.linear && law.doubled == 2.0 * law.pressure;
  checks.stokes = checks.stokes && lusine.laws.bulk_viscosity(viscosity = 0.0 Pa*s, sweep.divergence) == 0.0 Pa;
  lamps.colour = lamp_colour(holds = checks.heats && checks.odd && checks.linear && checks.stokes);
}

The law's other proof (the pressure against a reference over compressions and expansions) is on its proof page.