Quick Start¶
import numpy as np
import mrsimtracks as mt
from mrsimtracks.seeding import seed_mesh
flow = mt.load_flow("case.pvd", active_key="Velocity")
reseeder = mt.BoundaryReseeder(["Inlet.vtp", "Outlet.vtp"], flow, dt=0.002)
wall_slip = mt.WallSlip(flow, caps=["Inlet.vtp", "Outlet.vtp"])
seeds = seed_mesh(flow.active_mesh, 200_000, rng=np.random.default_rng(0))
result = mt.track(
flow,
seeds=seeds,
dt=0.002,
reseeder=reseeder,
wall_slip=wall_slip,
)
result.save("tracks.h5")
The saved HDF5 file contains:
position: particle positions with shape(n_steps, n_particles, 3)reset: reset flags with shape(n_steps, n_particles)dt: time step attribute
Streaming Output¶
For large single-process runs, pass output_path so timesteps are written
directly to HDF5 instead of accumulated in memory:
result, metrics = mt.track(
flow,
seeds=seeds,
dt=0.002,
reseeder=reseeder,
output_path="tracks.h5",
time_subsample=10,
return_metrics=True,
)
result.is_file_backed
metrics["particle_steps_per_s"]
With time_subsample=N, every Nth integration state is stored, dt records
the stored-state interval, and reset flags are accumulated over each interval.
Parallel Tracking¶
For larger runs:
result = mt.track_parallel(
"case.pvd",
seeds=seeds,
dt=0.002,
caps=["Inlet.vtp", "Outlet.vtp"],
active_key="Velocity",
n_workers=3,
)
Each worker reloads the field, so memory use scales with n_workers.
Ground-Truth Velocity Images¶
Sample a Cartesian image directly from the loaded CFD field:
image = mt.sample_velocity_image(
flow,
fov=(12.6, 2.6, 16.4), # full widths in native (x,y,z) mesh units
resolution=0.2, # isotropic voxel size in mesh units
temporal_spacing=0.030, # output time spacing in seconds
temporal_width=0.030, # exact boxcar average of the linear waveform
grid_subsampling=2, # 2 per axis = 8 samples per voxel
reorder_by_extent=False, # True for largest-to-smallest spatial axes
)
image.axis_order # "x,y,z", matching track output
image.velocity # (time, x, y, z, component)
image.occupancy # (x, y, z)
image.save("velocity_image.h5") # sparse spatial support by default
Use fov=None for the native mesh bounds. Three FOV widths expand symmetrically
about the mesh bounding-box center; three explicit (minimum, maximum) pairs
are also accepted. Neither form shifts the coordinate origin.
Set temporal_width=0 to linearly interpolate the exact output time instead of
averaging a window.
Wall Slip¶
Use WallSlip when interpolation near no-slip walls deposits particles into a
thin stuck layer. It removes only the into-wall velocity component for particles
inside a narrow wall band and leaves tangential motion unchanged:
wall_slip = mt.WallSlip(flow, caps=["Inlet.vtp", "Outlet.vtp"], band_frac=0.02)
result = mt.track(
flow,
seeds=seeds,
dt=0.002,
reseeder=reseeder,
wall_slip=wall_slip,
)
Pass the cap surfaces so inlet/outlet faces are not treated as walls. For
parallel tracking, set wall_slip=True and optionally wall_slip_band=0.02:
result = mt.track_parallel(
"case.pvd",
seeds=seeds,
dt=0.002,
caps=["Inlet.vtp", "Outlet.vtp"],
active_key="Velocity",
n_workers=3,
wall_slip=True,
)