Repository navigation
View Flow
Spacetime weather. Thousands of tracer particles are carried through a velocity field painted over a rubber sheet that memory bends into wells. Each solar system of the Orbit layout bends a bowl as wide as the system and deeper for more memory; busy processes stir whirlpools and shed particles in their colour; socket links become two-lane rivers between the processes that talk, one lane each way; connections that leave the machine send sparks rising off the sheet; exits burst outward; pressure adds turbulence. It is the fourth view (4), and the one whose picture is most easily misread as physics. This page says exactly what is solved, what is drawn, and where the "gravity" is a metaphor.


The short version of the truth: Flow integrates a first-order system,
Two real things are being imitated at once.
A weather map of fluid flow: a velocity field and the paths of tracers in it. Visualising a flow by releasing particles and drawing their recent tracks is as old as smoke-tunnel and dye photography; the computer version integrates
A gravity well on a rubber sheet: the standard teaching picture of general relativity (a mass dents a sheet; a marble rolls in). The picture is an embedding diagram of space; the textbook case is Flamm's paraboloid for the Schwarzschild spatial geometry (Flamm 1916), a surface of revolution
The thing Flow draws is a flow made visible by tracers, over a surface that is also a picture of gravity.
- Osborne Reynolds's experiment of 1883 injected dye into water running through a glass tube: at low speed the dye held a straight thread, and above a threshold it broke into eddies. The dimensionless number named after him came out of that comparison of regimes.
- Ludwig Prandtl's group at Gottingen filmed flows in water channels by scattering particles on the surface (aluminium powder, mica or lycopodium spores). The films date from around 1930 and were made largely for teaching, to show flow separation. Early on the exposures were so short that the particles appeared as dots, not the expected streaks, and the team changed to a camera with a longer exposure. Modern researchers at DLR later analysed the old films with particle image velocimetry and recovered the vortex strengths and speeds that Prandtl could not measure.
- The terms Flow uses, streamline, pathline and streakline, name three different curves that coincide only in a steady flow. Dye photographs show streaklines; a long photographic exposure of particles shows pathlines.
- The wells come from a different history. Ludwig Flamm's paper of 1916 embedded the spatial geometry of Schwarzschild's solution as a surface of revolution, now called Flamm's paraboloid. It is a rigorous picture, and sources that discuss it note that the popular rubber-sheet analogy resembles it only superficially and is a common source of misunderstanding. I did not trace who first drew the rubber sheet (
(unverified)). - Vortex blobs, the regularised vortex Flow uses, come from the numerical work of Chorin (1973), cited below. The Plummer sphere that softens the inflow was introduced for globular clusters (1911).
- Flow combines a flow-visualisation tradition and a cosmology teaching picture that were never meant to meet, and the tracers do not fall into the wells.
Flow's velocity field is a sum of elementary two-dimensional flows. With
-
Core-regularised vortex at
$\mathbf{c}$ , strength$\Gamma$ (a signed velocity-times-length) and core radius$r_c$ : $$ \mathbf{u}v = \frac{\Gamma}{d^2 + r_c^2},(-d_y,\ d_x), \qquad u\theta = \frac{\Gamma,d}{d^2 + r_c^2}. $$ Close in it is solid-body rotation,$u_\theta \approx (\Gamma/r_c^2),d$ ; far out it falls as$1/d$ , the irrotational vortex of potential flow with circulation$2\pi\Gamma$ . The speed peaks at$d = r_c$ , at$\Gamma/(2r_c)$ . The vorticity is$2\Gamma r_c^2/(d^2+r_c^2)^2$ , a smooth bump. This is the algebraic core of the vortex-blob methods (Chorin 1973) that replace the point vortex's$1/d$ singularity with something integrable; Lamb (1932) and Saffman (1992) have the family (Rankine, Lamb-Oseen and the rest). The field is divergence-free:$\partial_x u_x + \partial_y u_y = 0$ . -
Softened inflow.
$\mathbf{u}_s = -Q,\mathbf{d}/(d^2 + w^2)^{3/2}$ is the gravitational field of a Plummer sphere (Plummer 1911),$\mathbf{g} = -GM,\mathbf{d}/(d^2+\epsilon^2)^{3/2}$ , with$GM \to Q$ and$\epsilon \to w$ . Used as a velocity rather than an acceleration, this is the overdamped limit of motion in a potential:$\dot{\mathbf{x}} \propto -\nabla\Phi$ , a gradient flow with no orbits. Its maximum speed is at$d = w/\sqrt2$ and equals$0.385,Q/w^2$ . -
A jet lane
$\mathbf{u}_j = \mathbf{\hat t},S,e^{-\ell^2}\sqrt{\sin(\pi s/L)}\ \mathrm{sgn}(n)$ , along a segment of length$L$ with along-coordinate$s$ , signed cross-coordinate$n$ and lane coordinate$\ell = (\lvert n\rvert - 0.6,w_j)/(0.5,w_j)$ . The sign flips across the centreline: two opposite streams side by side, a shear layer, where the tangential velocity jumps by$2S e^{-1.44}$ across$n = 0$ . In a real fluid that vortex sheet would roll up (Kelvin-Helmholtz); in a prescribed field it just sits there, and the grid smooths it. -
Turbulence
$\mathbf{u}_t = A,(\sin(0.31,y + 0.8,t),\ \cos(0.29,x - 0.6,t))$ : two crossed travelling shear waves of wavelength about 20 world units and periods 7.9 s and 10.5 s. Each component depends on the other coordinate, so it is divergence-free too.
A two-dimensional, incompressible flow is a Hamiltonian system: with a stream function
Flow integrates each particle with the explicit midpoint (RK2) method:
Apply it to rotation at angular rate
Explicit Euler has
How big is dt = clamp(time - flow.time, 0, 0.1): at the default --fps 10 it is 0.1 s, at 30 fps 0.033 s, and slower frame rates are clamped (the flow runs in slow motion, not in big steps). The core rotation rate is weather (strength mass_radius of its subtree):
| process | peak speed |
|
growth per step | ||||
|---|---|---|---|---|---|---|---|
| 6 MiB at 400% | 0.44 | 1.87 | 60.5 | 17.3 rad/s | 16.2 | 1.73 | 1.80 |
| 64 MiB at 50% | 0.96 | 2.92 | 39.3 | 4.6 rad/s | 6.7 | 0.46 | 1.006 |
| 1 GiB at 100% | 2.42 | 5.84 | 48.9 | 1.4 rad/s | 4.2 | 0.14 | 1.0000 |
Measured with a stdlib port of the field and of Flow::advance (analytic field, speed capped at 20 as in weather): for the first case, tracers started anywhere from 0.3 to 1.9 units from the centre end up at radius 2.7 to 2.8 after 5 s at
A splash or birth gives a particle a kick velocity added to the field and decayed by
For a well of memory-proportional "mass"
| body | peak inflow | |||
|---|---|---|---|---|
| 53 MiB (the smallest that gets a dimple) | 0.9 | 0.44 | 2.8 | 0.02 units/s |
| 1 GiB | 2.42 | 8.5 | 5.84 | 0.10 units/s |
| 6.4 GiB or more (the radius clamp) | 4.5 | 54.7 | 10 | 0.21 units/s |
That is 0.02 to 0.2 units per second against a turbulent background of 0.4 to 6.4 and vortex speeds up to 20. The inflow is almost inert: memory affects where particles go only through the whirlpools' core radii (which scale with the body radius) and not through the "gravity". The wells do not catch particles. The Gaussian bowls of the systems themselves (inflow = 0) act on nothing but the height at which particles are drawn.
| Visual element | Measured quantity (platform source) | Transform | Status |
|---|---|---|---|
| Body (sphere) | one process | radius mass_radius(subtree memory), at the sheet's depth plus |
measured |
| Well under a system | memory of the system root's whole subtree (Layout::mass; RSS or footprint) |
Gaussian bowl of depth |
measured; saturating |
| Dimple under a heavy body | body radius above 0.9 (subtree memory above about 53 MiB) | depth |
measured; the inflow is nearly inert (above) |
| Sheet colour | relative depth |
|
derived, relative to the frame's deepest well |
| Whirlpool | CPU above 0.5% |
|
magnitude measured; handedness decorative (parity of the pid) |
| Particles from a process | CPU above 0.5% |
|
measured rate |
| River (two lanes) | socket link between two processes, its socket count |
width |
measured count and CPU |
| Rising sparks | connections leaving the machine (snapshot.outside) |
2 per connection per second, life 2.5 s, rising 2.5 units/s, pink | measured |
| Burst | an exit | 40 particles at 9 units/s | measured event |
| Puff | a birth | 12 particles at 3 units/s | measured event |
| Turbulence | pressure stall some avg10 of CPU, memory, I/O (Linux; macOS only the memory level) |
amplitude |
measured |
| Background tracers | none | 400 per second, dim blue, life 5 to 8 s | decorative: they reveal the field |
| Warm and green glows | CPU, GPU memory | as in Orbit | measured |
Here model::bounded.
Flow is a ViewModule in src/views/flow.rs; the state struct is called Wells, and its registry row in views::REGISTRY builds it with a closure. Wells owns a Galaxy (shared machinery with Orbit, in src/views/systems.rs) and an optional Flow sheet from src/medium.rs; Scene holds none of it. Wells::draw calls systems::hubs (hollow stars for unreadable parents, as in Orbit), then systems::layout, then Wells::pour, then Layout::report.
-
Layout.
systems::layout(&mut self.galaxy, stage, &processes, snapshot, 0.0): the same layout code as Orbit with the orbital motion held at zero (planets sit at their starting mean anomalies, the galaxy does not turn). Each view owns its ownGalaxy, so a system's centre in Flow is where Flow's galaxy placed it, which agrees with Orbit's only while neither has turned it; individual planets are not where Orbit draws them at the same moment, except at time zero. (The README says "a process sits in the same place in each"; strictly that holds for the system centres.) -
Grid.
footprint(&layout.bodies, 140), which callsrender::cover: 140 cells on the longer side with a margin of $8 + 0.08\times$extent, kept ifrender::similarto the previous grid and rebuilt otherwise. -
Step.
dt = clamp(time - flow.time, 0, 0.1). -
Field.
weather(a free function in the module) runs when the cached field is older than 0.25 s (field_time). It zeroes the velocity and depth grids and adds, each only over the cells within its reach (around): the wells (Gaussian depth plus softened inflow), the vortices, the jets of the links; then caps every cell's speed at 20 and every depth at$-20$ . The field is cached on the grid; a particle samples it by bilinear interpolation (Grid::sample). -
Emission. Fractional rates are realised by a Bernoulli trick,
while expected > random() { expected -= 1; spawn }, whose mean count is exactlyexpected. The random stream is one seededXorShift32(GOLDEN_RATIO_32), and the iterations that use it run in a fixed order (bodies in layout order,snapshot.outsidesorted by identity) so a frame is repeatable. The total is capped at 6000 particles: once it is reached,roomstops emission in the order of the loops (background tracers first, then bodies in layout order, then links, then sparks); only the exit bursts and birth puffs skip the check. -
Advection.
Flow::advance(dt, stir): for each particle, RK2 midpoint on $\mathbf{u}\mathrm{field} + \mathbf{u}\mathrm{turbulence} + \mathbf{k}$, then$\mathbf{k} \leftarrow \mathbf{k},e^{-2.5\Delta t}$ ; the trail rotates by one and its head takes the new point at the sheet's depth plus 0.15 (a rising spark gains 2.5 units/s instead). Particles are removed when their age passes their life or they leave the grid. The turbulence amplitude is$0.4 + 6,\mathrm{stir}$ withstir$= b(\text{cpu} + \text{memory} + \text{io pressure}, 25)$ . -
Draw. The sheet is a wireframe of every third grid row and column, coloured by depth; then the bodies; then, for each particle,
TRAIL - 1= 19 line segments (Frame::beam) fading along the trail with weight$s(1 - k/20)$ ,$s = (0.9\ \text{or}\ 0.6)\cdot\min(\mathrm{age}/0.3, 1)\sqrt{1 - \mathrm{age}/\mathrm{life}}$ , and a small glow on the bright ones. -
Time. The view takes
stage.timeand clamps the step itself (step 3), notstage.dt. Because the trail holds 20 positions, one per frame, a trail is$20,\Delta t$ of history: two seconds at 10 fps, two thirds of a second at 30. Streak length is local speed times that: Flow's picture is a fixed-exposure photograph whose exposure shortens as the frame rate rises.
Picking. Bodies carry their index in the pick buffer; the sheet and the particles do not. Hover names a process only over a body.
Stability. Like Orbit, Flow's layout re-places a system only when its structure outgrows the reservation (HEADROOM in systems.rs). The row waives no property; the harness checks each process's home, the centre of its system, which Wells returns from ViewModule::anchors (test-only). The flow media survive layout changes while the grid is similar.
Headless. The row's SETTLE warm-up (80 frames of 0.05 s ending at --time) lets particles fill the sheet.
What degrades. No optional source. On macOS the turbulence term sees only the memory-pressure level (0, 25 or 75); there are no green GPU glows; unreadable parents become hollow stars. Without socket links there are no rivers (and no sparks if no connection leaves the machine).
What the demo render shows: a dark violet-blue sky above a faint blue wireframe sheet, twelve bowls (one per system), each with its star near the bottom and its planets on the slopes; coloured streaks (in the kinds' colours: teal, amber, violet, slate) piled into coils around the systems, thin pink streaks where connections leave, and a field of short, dim blue streaks wandering over the flat sheet between systems. Near each star the tracks curl into closed loops; some coils look hollow, with a ring of streaks and an empty middle, which is the integrator effect described above on the small busy processes.
You can infer
- Where memory is concentrated: the deepest and widest bowls are the systems with the most memory in their trees, with diminishing returns at the top (the depth saturates, and the colour is normalised to the frame's deepest well).
-
Which processes are busy: they are the ones with whirlpools and the densest particle clouds; the particle count is roughly proportional to
$1 + 30,u/(u+40)$ . - Which processes talk: a river between two bodies, with its width and brightness following the socket count.
- Pressure, from the background tracers' wandering: the amplitude of turbulence rises from 0.4 to as much as 6.4 units/s.
- Births and exits, as a small burst of light-coloured streaks or a ring of 40 at 9 units/s.
You cannot infer
- Whether particles are falling into a well. They are not; the dip is a drawing.
- Handedness. A clockwise whirlpool is not different from an anticlockwise one; it is the parity of a pid.
- Absolute velocities from streak length. Streak length is speed times the trail's duration (20 frames), and so depends on the frame rate.
- Which of two overlapping systems a particle belongs to: colour is the emitter's, and the field mixes.
-
The count of busy processes when many are busy. The 6000-particle cap binds in a busy session (in the demo at
--time 12, 126 sources average 13.5 particles per second each; with 4 to 7 s lives and 400 background tracers per second the steady-state demand from the formulas is about 12 000 particles), and emission is then first-come in layout order, so busy processes late in the order may be starved. I did not measure the starvation; it follows from the code. - A sheet that matches the processes' memory beyond the system level. Only the systems and the heavy bodies (above 53 MiB) have wells.
- Anything below 0.5% CPU.
Resolution. The field lives on a 140-cell grid, so features smaller than a cell or two are smoothed; the smallest vortex core (1.5 units) is about the size of a cell on a 200-unit layout. The jet lanes' centreline discontinuity is smoothed by the same grid.
./target/release/isotop --demo --view flow
./target/release/isotop --demo --view flow --width 1600 --output flow.png --renderer cpu
./target/release/isotop --demo --view flow --width 1600 --renderer gpu --benchmark 120
./target/release/isotop --demo --view flow --fps 30 # a third of the trail length in time
./target/release/isotop --view flowKeys: 4 selects it; click a body for the inspector; f focuses a subtree; c cycles link arcs (the rivers are the flow's own depiction, so the scene's arcs show only for highlighted processes).
-
src/views/flow.rs:Wells,Wells::draw,Wells::pour,Pour(background,around_bodies,along_links,sparks),disturb,weather,fabric,bodies,particles,footprint,Lane,PARTICLES; tests in the same file andfocusing_one_process_after_another_leaves_no_orbit_or_flow_system_behindinsystems.rs. -
src/views/systems.rs:layoutandhubs(shared with Orbit). -
src/render.rs:cover,similar,glows,mass_radius,Stage::newborn,Stage::splashes. -
src/medium.rs:Flow(new,spawn,advance,velocity_at,depth_at),Particle,TRAIL,Grid::sample; testparticles_circle_a_vortex_and_ride_a_jet. -
src/simulation.rs:XorShift32,GOLDEN_RATIO_32. -
src/views.rs: theflowrow (SETTLE); the legend isWells::legend. - Figure script:
docs/figures/flow-vortex-integrators.py.
- A symplectic or exact vortex step. Replace the RK2 step by the implicit midpoint rule or by an exact rotation about the nearest vortex: the hollow cores would fill, and the cost is a few iterations.
-
Real inertial particles. Give each tracer a velocity and relax it to the field with
$\tau$ (Stokes drag), plus the gradient of the sheet as a force: now the wells do pull, and tracers can be flung out of vortices by their inertia. The right integrator is then a Verlet or Boris-style kick-drift-kick. - Let the vortices move each other. Point vortices in a plane advect one another by the Biot-Savart law and are a Hamiltonian system of their own; for four or more the motion is chaotic (Aref 1983). Letting the CPU whirlpools drift under each other's fields would make the stir dynamic.
- A real fluid. Replace the prescribed field by a stable-fluids solver (Stam 1999) forced by CPU and sockets; the shear layers between the lanes would then roll up on their own.
- Streaklines. Release particles at a point continuously and join them: a dye photograph of one process's trace.
- Fix the cap's bias. Share the 6000 budget in proportion to demand, or let each emitter's life shorten with load; check the demo for starved systems.
- Open: is there a depth profile for the wells whose slope, used as a force, gives bounded orbits of the right character (Flamm's paraboloid has none)?
- H. Lamb, Hydrodynamics, 6th ed. (Cambridge University Press, 1932).
- P. G. Saffman, Vortex Dynamics (Cambridge University Press, 1992).
- A. J. Chorin, "Numerical study of slightly viscous flow", Journal of Fluid Mechanics 57 (1973), 785-796.
- H. C. Plummer, "On the problem of distribution in globular star clusters", Monthly Notices of the Royal Astronomical Society 71 (1911), 460-470.
- L. Flamm, "Beitrage zur Einsteinschen Gravitationstheorie", Physikalische Zeitschrift 17 (1916), 448-454 (unverified page range).
- M. R. Maxey and J. J. Riley, "Equation of motion for a small rigid sphere in a nonuniform flow", Physics of Fluids 26 (1983), 883-889.
- H. Aref, "Integrable, chaotic, and turbulent vortex motion in two-dimensional flows", Annual Review of Fluid Mechanics 15 (1983), 345-389.
- J. Stam, "Stable fluids", Proc. SIGGRAPH '99 (1999), 121-128.
- E. Hairer, C. Lubich, G. Wanner, Geometric Numerical Integration, 2nd ed. (Springer, 2006).
isotop — maximalism for its own sake. Source: tfpickard/isotop. This wiki is published from wiki/ in the repository; edit it there. This footer is decorative.
Philosophy · Gallery · Glossary
Theory
Overview
Sampling: Linux
Sampling: macOS
Background sampler
Model and rates
Life events
History
Journal
Scene and display list
Camera and projection
Rasterization
GPU and parity
Picking
Terminal graphics
Stable layout
Determinism
Simulation toolkit
Colour
Text safety
Mathematics
Fourier epicycles
Wave equation
Flocking
Spatial hashing
Stellar photometry
Lorentz and Schlegel
Reaction-diffusion
Spiking neurons
Schwarzschild
Views
City · Orbit · Ripple
Flow · Cores · Cells
Strata · Globe · Reef
Coop · Matrix
Ptolemy · Tesseract
Necropolis · Cortex
Horizon · Murmuration
Arbor · HR
Hive · Switchboard
Armorial · Lights
Planned
Mold · Chamber
Anthill · Plumbing
Morph · Ouija
Canyon · Die
Guides
Writing a view
Recipes
Headless renders
Contributing to the wiki