Stopping trajectories#
Fortran TRACMASS stops (“kills”) trajectories when they exceed a maximum
time (timax), reach the surface, or enter a kill zone (exitType 1–4,
set up in kill_zones.F90). TRACMASS-JAX does not have kill zones yet.
Particles stop when they exceed their lifetime, leave the domain or hit
land, or when a safety check stops them. Every stopped particle records
why.
Reason codes#
ParticleState.reason (int16) holds the reason. The constants are exported
from tracmass:
Code |
Constant |
Name in tallies and files |
When |
|---|---|---|---|
0 |
|
|
Still running, or paused at a window boundary |
1 |
|
|
|
2 |
|
|
Moved into a land cell ( |
3 |
|
|
Left the domain horizontally, or entered the edge ring of a non-periodic axis |
4 |
|
|
A sub-cycle made no progress in time |
5 |
|
|
Fell too far behind the window’s pace |
6 |
|
|
The window reached |
7 |
|
|
The cell volume was zero or negative |
8 |
|
|
Left the domain vertically |
The monitor prints a tally per window, for example
stopped:3 (land:2, out_of_bounds_h:1). The recorder writes one
event_type="deactivated" row per stopped particle, with its position and
reason (see Output).
import numpy as np
import tracmass
result = tracmass.time_dependent(state, grid, lazy)
reasons = np.asarray(result.reason)
print((reasons == tracmass.REASON_LAND).sum(), "particles hit land")
Maximum trajectory time — traj_max_time#
The counterpart of Fortran’s timax. traj_max_time stops each particle a
fixed time after its own release (reason time_limit):
[myrun]
traj_max_time = "90D" # Timedelta strings: "30D", "12h", "2W", "90min"
tracmass.time_dependent(state, grid, lazy, traj_max_time="90D")
tracmass.time_dependent(state, grid, lazy, timax_seconds=86400.0) # in seconds
It also accepts calendar units: "1M", "6M", "1Q", "1Y". pandas does
not allow M or Y in a Timedelta, so here they always mean months and
years; use "min" for minutes. A calendar month has no fixed length, so
its length is measured once, from the run’s start (backward for a backward
run). "1M" from 2010-02-01 is 28 days, and every cohort of a
repeat-seeded run gets those same 28 days.
The argument wins over the setting. timax_seconds and traj_max_time
cannot both be passed. A particle stops at the first sub-step that ends
past its limit, so it can overshoot by up to one sub-step
(timestep / iter_sub). The file metadata gets traj_max_time and
timax_seconds.
Leaving the domain#
On every axis that does not wrap around (iperio = 0, jperio = 0), a
particle that enters the outermost ring of cells stops with
out_of_bounds_h. The flux through the outer faces of those cells is not
part of the grid, so they cannot be integrated correctly. This matters
mostly for subsets:
from tracmass.gcm.flux_grid import edge_mask
edge = edge_mask(grid) # (imt, jmt) bool, True on the edge ring
Leave a margin of at least one cell between the seeding box and the edges of the subset. Particles seeded in an edge cell stop on their first step.
Land#
A particle that moves into a cell with kmt == 0 stops with land. With
correct fluxes this should not happen, because the flux into a land cell is
zero. The kernel also forces zero flux on every face next to land. A small
number of land stops (around 0.001–0.003 % of particles per daily window
in a global GLORYS12 run) remains, from particles that leave a cell through
a corner and from rounding. See the README section Coastline accuracy.
Safety stops#
These stop particles that would otherwise hold up the whole run. Fortran handles such particles one at a time. Here all particles share the sub-cycle loop, so a few stuck particles would make every particle wait.
no_progress: a sub-cycle that did not advance the particle’s time, usually a particle stuck exactly on a wall in a convergence.too_slow: after two warm-up sub-cycles, a particle must keep up a pace of one window per(max_sub_iter_per_window - 2) / pace_factorsub-cycles. A largerpace_factoris stricter. The default, 5, means a particle has to finish the window within about a fifth of the sub-cycle limit.iter_limit: the window reachedmax_sub_iter_per_window(default 10 000) sub-cycles. ARuntimeWarningis raised, and the monitor marks the window[LIMIT].
If many particles stop with these reasons, look at the fluxes near where
they stopped, or try more sub-steps (iter_sub).
Kill zones#
Box and tracer kill zones (Fortran exitType 1–4) are not ported yet. Two
workarounds:
Run on a subset whose edge is the box you want: particles stop with
out_of_bounds_hwhen they reach it.Post-process: load the trajectories and cut each one at the first time it enters your region:
df = tracmass.load(env="myrun", lonlat=True) inside = df.lat > 60 first = df[inside].groupby("id").dtm.min().rename("t_exit") df = df.join(first, on="id") df = df[df.t_exit.isna() | (df.dtm <= df.t_exit)]