# Seeding trajectories Seeding creates the initial `ParticleState`. In Fortran TRACMASS this is set by the `INIT_SEEDING` namelist group (seeding boxes `ist1`…`kst2`, `SeedType`, `nqua`, `partQuant`, seed files). In TRACMASS-JAX you call a function in `tracmass.seed`, or describe the seeding in an `[env.seed]` settings table. All seeding functions return particles at time zero (`tt = t0 = 0`), active, with `reason = 0`. ## Index conventions - `i`, `j`, `k` are **1-based** TRACMASS cell indices, as in Fortran. - With a `grid` that comes from a subset, the `i`/`j` you pass are **absolute** (full-grid) indices. They are converted with `grid.i_offset` / `grid.j_offset`. Pass `use_subset_coordinates=True` to give them relative to the subset instead. - `k = 1` is the first model level. Cells with `k > kmt` (below the sea floor) and land cells are skipped when a `grid` is given. ## Seeding from boxes — `seed.ijk` The counterpart of seeding from namelist boxes (`ist1`, `ist2`, `jst1`, `jst2`, `kst1`, `kst2`). One particle at the centre of every ocean cell in the box: ```python from tracmass import seed state = seed.ijk(10, 20, 30, 40, 1, 1, grid=grid) # 11 x 11 cells, level 1 state = seed.ijk(10, 20, 30, 40, 1, 5, grid=grid) # levels 1-5 state = seed.ijk(10, 20, 30, 40, 1, 1, grid=grid, use_subset_coordinates=True) # box relative to the subset ``` From settings: ```toml [myrun.seed] approach = "ijk" box = { i1 = 2610, i2 = 2620, j1 = 510, j2 = 520, k1 = 1, k2 = 1 } ``` ## Number of particles per cell Fortran sets the number of particles per cell with `nqua` and `partQuant`. TRACMASS-JAX places them on a regular sub-grid inside each cell, with `sub_dist="rect_xyz"` (the default) and the number in each direction: ```python state = seed.ijk(10, 20, 30, 40, 1, 1, grid=grid, i_sub_num=4, j_sub_num=4, k_sub_num=1) # 16 per cell ``` The particles sit at the centres of an `i_sub_num × j_sub_num × k_sub_num` sub-division of the cell, so with 1 in each direction (the default) there is one particle at the cell centre. Every seeding function except `latlon_list` accepts these options, and so does a settings table: ```toml [myrun.seed] approach = "ijk" sub_dist = "rect_xyz" i_sub_num = 40 j_sub_num = 40 k_sub_num = 1 box = { i1 = 2610, i2 = 2620, j1 = 510, j2 = 520, k1 = 1, k2 = 1 } ``` Each particle carries `subvol` (Fortran's transport per trajectory). Seeding by transport (Fortran `nqua = 2, 3`) is not implemented yet. :::{note} `sub_dist="gaus_xyz"` (Gaussian sub-cell positions) is not working yet; it is marked as an expected failure in `tests/test_seed.py`. ::: ## Seeding in geographic coordinates These approaches need an adapter with a geographic grid (`glorys`, `ecco2`, `scrippsgog`). The adapter is taken from `gcm=` or from `[env.gcm] name`. ### A lat/lon box — `seed.latlon_box` Every ocean cell whose centre lies in the box: ```python state = seed.latlon_box(-64, -62, -70, -68, grid=grid, k1=1, i_sub_num=10, j_sub_num=10) ``` Longitudes run eastward from `lon1` to `lon2` in any convention, so `lon1 > lon2` gives a box across the date line (e.g. `170` → `-170`). ```toml [myrun.seed] approach = "latlon_box" i_sub_num = 10 j_sub_num = 10 box = { lat1 = -64, lat2 = -62, lon1 = -70, lon2 = -68, k1 = 1, k2 = 1 } ``` ### Around a point — `seed.latlon_point` A box of half-width `dlat` (and `dlon`, default `dlat`) around a point: ```python state = seed.latlon_point(-63.0, -69.0, dlat=0.5, grid=grid) ``` ```toml [myrun.seed] approach = "latlon_point" lat = -63.0 lon = -69.0 dlat = 0.5 ``` ### At exact positions — `seed.latlon_list` One particle at each listed position, at the exact fractional position rather than the cell centre. A point on land moves to the centre of the nearest ocean cell. ```python import pandas as pd df = pd.DataFrame({"lat": [-63.0, -62.5], "lon": [-69.0, -68.2]}) state = seed.latlon_list(df, grid=grid) # level kpos=1 state = seed.latlon_list(seed_file="stations.csv", grid=grid) ``` ```toml [myrun.seed] approach = "latlon_list" seed_file = "stations.csv" # lat, lon (and optionally k) columns kpos = 1 # level when the file has no k column # or inline: lat = [-63.0, -62.5] lon = [-69.0, -68.2] ``` ## Seeding from a mask — `seed.mask` The counterpart of Fortran's `maskfile`. One particle (or one sub-grid) per `True` cell of a boolean array: ```python from tracmass.seagrid import ecco2 kmt = ecco2.grid_definition().kmt.values # (jmt, imt), as read from file state = seed.mask(kmt > 0, kpos=1) # every ocean cell at level 1 ``` :::{important} The mask must be in the data's own orientation, **latitude first** `(jmt, imt)` (or `(jmt, imt, km)` for 3-D). This is the opposite of `GridFields.kmt`, which is `(imt, jmt)`. A warning is raised if the first axis is longer than the second, which usually means the mask was transposed. Use `seed.ijk(..., grid=grid)` to seed from `grid.kmt`. ::: ## Seeding from a file — `seed.from_df` The counterpart of Fortran's seed file (`SeedType = 2`). Cells are given by the integer columns of a `DataFrame`: ```python df = pd.read_csv("seed_cells.csv") # columns i, j, k state = seed.from_df(df, i_col_name="i", j_col_name="j", k_col_name="k") ``` `seed.from_df_xyz` takes fractional positions instead, and places the particles exactly there. ## Repeated releases — `seed_freq` Fortran releases particles at several times with `tst1`, `tst2` or a `timeFile`. In TRACMASS-JAX, `seed_freq` releases the initial particles again every `seed_freq`, a pandas frequency counted from `dtm_start` (backward in time for a backward run): ```toml [gog_upwelling.seed] approach = "ijk" seed_freq = "30D" # or "7D", "MS" (month starts), "QS", ... box = { i1 = 3323, i2 = 3589, j1 = 1390, j2 = 1603, k1 = 23, k2 = 23 } ``` ```python tracmass.time_dependent(state, grid, lazy, seed_freq="MS") df = tracmass.load(env="gog_upwelling") df.groupby("t0").id.nunique() # one cohort per release ``` - Each release is a copy of the initial state, appended after the existing particles at the start of the window the seed time falls in. A seed time inside a window moves to the next window start, with a warning. - A released particle's clock starts at the release (`tt = t0 =` release time), so `t0` tells the cohorts apart, and `traj_max_time` counts from each particle's own release. - Existing particle ids do not change, and new particles get the next unused ids. - The run keeps going through windows with no active particles while a release is still to come. - The file metadata gets `seed_freq` and `seed_windows`. - Each release makes the particle arrays bigger, so the kernel recompiles once per release. ## Seeding with a function in `tracmass.run` `tracmass.run` also accepts a function of the grid, which is called once the grid exists. This is handy when the seeding needs the grid but you still want the rest of the run to come from settings: ```python result = tracmass.run( "2020-07-01", env="tempvel", state=lambda grid: seed.ijk(10, 20, 10, 20, 1, 1, grid=grid), ) ```