Particles and dye of a simulation¶
Tracer particles and a numerical dye that live inside a native simulation: they advance with each
step of sim.run, on the CPU or the GPU, and write files in the cn.io layout.
Tracers¶
import numpy as np
import conatus as cn
box = cn.Box(x=cn.periodic(4.0), y=cn.walls(-1, 1))
m = cn.Model(box, name="plane Couette", nu=0.1)
m.velocity("u"), m.pressure("p")
m.equations("dt(u) + grad(p) - nu*lap(u) = -u@grad(u)", "div(u) = 0")
m.at("bottom", u=(-1, 0))
m.at("top", u=(1, 0))
sim = m.simulate(N=(32, 32), scheme="RK443", dt=0.01)
sim.initial(u=lambda x, y: (y, 0 * x))
tracers = sim.particles(np.array([[0.0, -1.0], [2.0, 0.3]]), path="tracers.h5", every=50,
velocity="u", at_walls="refuse")
sim.run(until=3.0)
print(tracers.explain())
X = tracers.positions(unwrapped=True) # (P, d), coordinate order
What is computed, and declared by explain():
Velocity at a tracer: the spectral series of the velocity evaluated exactly at the point (all resolved modes: cos/sin along periodic axes, Chebyshev \(T_n\) across the walls), from the coefficients of the state; never an interpolation of grid values. The cost is \(P \times\) the number of modes per evaluation (four per step), in chunks of particles.
Time integration: one classical RK4 step per flow step. Its end velocities are the flow states before and after the step; the midpoint velocity is interpolated in time through the last four flow states. Order 4 in a steady flow; in an unsteady one, never better than the order of the flow scheme itself, whose time error the flow states carry (3 with RK443), and the first step of each
runinterpolates linearly between two states (also third order).Periodic axes: positions are kept unwrapped (
positions(unwrapped=True), for dispersion) and also given wrapped into \([a, a + L)\).Walls: a step that ends outside the walls raises a
NumericalErrornaming the particle (at_walls="refuse", the default) or puts it back on the wall and counts it (at_walls="clip",wall_clips(), in the file and inexplain()). A tracer on a no-slip wall stays on it; a tracer on a moving wall moves with it. RK4 stage positions are not clipped.File (
path=,every=):position,position_unwrapped,velocitywith dimensions(t, particle, component)andwall_clipswith(t, particle);cn.io.read(path).to_xarray().Not covered (refused with the reason): curved and mapped geometries, sine/cosine walls, half lines and lines, compound intervals, complex-valued problems (
periodic(L, complex=True)). Positions are not in the simulation’s checkpoints: after a restart, declare the tracers again (from the last row of the file to continue them).
Dye¶
import numpy as np
import conatus as cn
box = cn.Box(x=cn.periodic(4.0), y=cn.walls(-1, 1))
m = cn.Model(box, name="plane Couette with dye", nu=0.1)
m.velocity("u"), m.pressure("p")
m.equations("dt(u) + grad(p) - nu*lap(u) = -u@grad(u)", "div(u) = 0")
m.at("bottom", u=(-1, 0))
m.at("top", u=(1, 0))
m.dye("c", velocity="u", kappa=0.01)
sim = m.simulate(N=(32, 32), scheme="RK443", dt=0.01)
sim.initial(u=lambda x, y: (y, 0 * x), c=lambda x, y: np.exp(-((x - 2) ** 2 + y ** 2) / 0.3))
out = sim.dye("c", "dye.h5", every=50)
sim.run(until=3.0)
mass = cn.io.read("dye.h5").tasks["mass"]
print(out.explain())
print("relative mass drift:", abs(mass[-1] - mass[0]) / abs(mass[0]))
m.dye("c", velocity="u", kappa=...) declares one more unknown and its equation in conservative
form, \(\partial_t c - \kappa \nabla^2 c = -\nabla\cdot(c\,\boldsymbol u)\), with the no-flux condition
\(\partial_n c = 0\) on the walls when \(\kappa > 0\) (both printed by m.explain()). The dye is advanced
by the simulation’s own scheme: diffusion implicit, advection explicit, the same dealiasing and the
same time step, on the CPU or the GPU. kappa is a number or the name of a declared parameter.
kappa=0 is pure advection: no diffusion, no filter, no hyperviscosity, no wall condition; the dye’s
spectral tail then grows as its filaments thin, and the file records it.
sim.dye(name, path, every=) writes the dye (grid values; field=False for the diagnostics only)
and, at each row: mass (\(\int c\,dV\) of the series), variance (\(\int (c - \bar c)^2\,dV\)),
minimum and maximum of the grid values (under- and overshoots of an under-resolved dye), and
tail (largest relative coefficient among the last two modes of each axis). Mass is conserved by
the equation; what the discretization keeps is measured: to rounding in a periodic box, to the
level of the tau correction of the no-flux condition with walls (reported in the validation table of
the paper).
On the GPU¶
m.simulate(..., backend="cuda"): the tracers’ positions and the evaluation of the series stay on
the device; positions() and the files copy to the host. The test
tests/model/test_particles_gpu.py compares GPU and CPU runs. Not yet verified on GPU hardware:
that test has not run yet, so treat the GPU path of tracers and dye as experimental until it has.
Two interfaces¶
|
|
|
|---|---|---|
Velocity |
the running simulation, its exact spectral series |
any callable, or grid snapshots with a declared interpolation |
Time step |
one RK4 step per flow step, past flow states for the midpoint |
the caller’s |
Walls |
|
|
Output |
HDF5 file ( |
arrays in memory |
Use sim.particles while a native simulation runs; use the diagnostics module for a velocity you
own or for data after a run.
See also: Diagnostics and visualization (tracers from snapshots or analytic velocities, inertial particles, streaklines, timelines, FTLE, mixing), tutorial 16. Lagrangian tracers and a numerical dye.