Source code for tracmass.seed

import warnings

import jax.numpy as jnp
import numpy as np
import pandas as pd

from tracmass.particle import ParticleState
from tracmass.gcm import FluxDataset, GridFields


[docs] def ijk( i1: int, i2: int, j1: int, j2: int, k1: int, k2: int, grid: GridFields | None = None, use_subset_coordinates: bool = False, **sub_kw, ) -> ParticleState: """Seed particles in every cell of an i/j/k index box. Parameters ---------- i1, i2, j1, j2, k1, k2 : int 1-based TRACMASS cell index ranges (inclusive). By default *i* and *j* are absolute, full-grid indices -- the frame of saved trajectories -- whatever ``subset`` the grid was loaded with. grid : GridFields or None, optional The run grid. Its ``i_offset`` / ``j_offset`` convert absolute indices to the subset-relative ones particles use while integrating, cells outside the grid are dropped, and land cells (``kmt == 0``) are skipped. Without a grid the indices are used as given (no offset, no masking). use_subset_coordinates : bool, optional If True, *i*/*j* are already subset-relative (cell 1 is the subset's first cell) and no offset is applied. Default False. From settings: ``use_subset_coordinates`` in ``[env.seed]``. **sub_kw Sub-cell options (``sub_dist``, ``i_sub_num``, ...). Returns ------- ParticleState Particles in subset-relative indices. Raises ------ ValueError If *grid* is given and no cell of the box lies inside it. Examples -------- >>> # glorys subset i1 = 2600, j1 = 500: absolute cells 2611..2621 >>> state = seed.ijk(2611, 2621, 511, 521, 1, 1, grid=grid) >>> # the same cells in subset coordinates >>> state = seed.ijk(11, 21, 11, 21, 1, 1, grid=grid, ... use_subset_coordinates=True) """ iarr, jarr, karr = np.mgrid[i1 : i2 + 1, j1 : j2 + 1, k1 : k2 + 1] ivec, jvec, kvec = iarr.flatten(), jarr.flatten(), karr.flatten() if grid is not None: if not use_subset_coordinates: ivec = ivec - int(grid.i_offset) jvec = jvec - int(grid.j_offset) inside = (ivec >= 1) & (ivec <= grid.imt) & (jvec >= 1) & (jvec <= grid.jmt) if ivec.size and not inside.any(): frame = "subset" if use_subset_coordinates else "absolute" raise ValueError( f"seed.ijk: box i {i1}..{i2}, j {j1}..{j2} ({frame} " f"coordinates) lies outside the grid, which spans absolute " f"i {grid.i_offset + 1}..{grid.i_offset + grid.imt}, " f"j {grid.j_offset + 1}..{grid.j_offset + grid.jmt}" ) ivec, jvec, kvec = ivec[inside], jvec[inside], kvec[inside] # i/j are 1-based TRACMASS cell indices; kmt is indexed 0-based. mask = np.asarray(grid.kmt).astype(bool)[ivec - 1, jvec - 1] ivec, jvec, kvec = ivec[mask], jvec[mask], kvec[mask] return _expand_sub(ivec, jvec, kvec, **sub_kw)
[docs] def from_settings(grid: GridFields | None = None, cfg=None) -> ParticleState: """Seed particles as described by the ``[env.seed]`` settings table. Parameters ---------- grid : GridFields or None, optional Passed on to the seeding function (``ijk`` uses it to skip land). cfg : mapping or None, optional The seed table. Default: ``seed`` from the active ``tracmass.config.settings`` environment. Returns ------- ParticleState Raises ------ ValueError If there is no seed table, or its ``approach`` is not a key of :data:`SETTINGS_APPROACHES`. Notes ----- The table names an ``approach`` and gives its arguments in a ``box`` sub-table; every other key (``sub_dist``, ``i_sub_num``, ...) is passed on as a sub-cell seeding option:: [omi.seed] approach = "ijk" sub_dist = "rect_xyz" i_sub_num = 40 j_sub_num = 40 k_sub_num = 1 box = { i1 = 10, i2 = 20, j1 = 10, j2 = 20, k1 = 1, k2 = 1 } ``i``/``j``/``k`` in ``box`` are 1-based TRACMASS cell indices, with ``i``/``j`` absolute (full-grid) unless the table sets ``use_subset_coordinates = true`` (see :func:`ijk`). With ``approach = "latlon_box"`` the box is geographic instead (see :func:`latlon_box`):: box = { lat1 = -64, lat2 = -62, lon1 = -70, lon2 = -68, k1 = 1, k2 = 1 } and ``approach = "latlon_point"`` takes ``lat``, ``lon``, ``dlat`` (and optionally ``dlon``, ``k1``, ``k2``). ``approach = "latlon_list"`` seeds one particle per point, from a table file (``seed_file = "points.csv"`` with ``lat``/``lon`` columns) or inline ``lat = [...]`` / ``lon = [...]`` lists (see :func:`latlon_list`). Examples -------- >>> tracmass.settings.setenv("omi") >>> state = seed.from_settings(grid) """ if cfg is None: from tracmass.config import settings cfg = settings.get("seed", None) if not cfg: raise ValueError( "seed.from_settings: no [env.seed] table in the active settings" ) kw = {str(k).lower(): v for k, v in dict(cfg).items()} approach = kw.pop("approach", "ijk") kw.pop("seed_freq", None) # repeat seeding, read by the integrators try: fn = SETTINGS_APPROACHES[approach] except KeyError: raise ValueError( f"seed.from_settings: unknown approach {approach!r}; " f"expected one of {sorted(SETTINGS_APPROACHES)}" ) from None box = {str(k).lower(): v for k, v in dict(kw.pop("box", None) or {}).items()} use_subset = bool(kw.pop("use_subset_coordinates", False)) if fn is ijk: kw["use_subset_coordinates"] = use_subset return fn(**box, **kw, grid=grid)
def _grid_lonlat(grid: GridFields, gcm: str | None): """Cell-centre ``(lon, lat)`` of *grid* from the GCM adapter, ``(imt, jmt)``.""" import importlib if gcm is None: from tracmass.config import gcm_name gcm = gcm_name() if gcm is None: raise ValueError( "seed: lat/lon seeding needs the GCM; pass gcm=... or set " "[env.gcm] name" ) module = importlib.import_module(f"tracmass.gcm.{gcm}") if not hasattr(module, "lonlat_grid"): raise ValueError( f"seed: GCM {gcm!r} has no lonlat_grid; lat/lon seeding needs an " f"adapter with a geographic grid (glorys, ecco2, scrippsgog)" ) return module.lonlat_grid(grid)
[docs] def latlon_box( lat1: float, lat2: float, lon1: float, lon2: float, kpos: int = 1, grid: GridFields | None = None, *, k1: int | None = None, k2: int | None = None, gcm: str | None = None, **sub_kw, ) -> ParticleState: """Seed every ocean cell whose centre lies in a lat/lon box. Parameters ---------- lat1, lat2 : float Latitude range in degrees (inclusive). lon1, lon2 : float Longitude range in degrees (inclusive), eastward from *lon1* to *lon2*: any longitude convention works, and ``lon1 > lon2`` gives a box across the dateline (e.g. ``170`` → ``-170``). kpos : int, optional 1-based level to seed when *k1* is not given. Default 1. grid : GridFields The run grid (required): selects the window of the GCM's lon/lat (for a ``subset``) and its ``kmt`` masks out land. k1, k2 : int or None, optional 1-based level range (inclusive), as in :func:`ijk`; *k2* defaults to *k1*. Levels below the local bottom (``k > kmt``) are skipped. gcm : str or None, optional Adapter providing ``lonlat_grid``. Default: ``[env.gcm] name``. **sub_kw Sub-cell options (``sub_dist``, ``i_sub_num``, ...), see :func:`ijk`. Returns ------- ParticleState Raises ------ ValueError If *grid* is missing, the GCM has no geographic grid, or the box contains no ocean cell of the grid. Examples -------- >>> state = seed.latlon_box(-64, -62, -70, -68, grid=grid, k1=1, ... i_sub_num=10, j_sub_num=10) From settings:: [omi.seed] approach = "latlon_box" i_sub_num = 10 box = { lat1 = -64, lat2 = -62, lon1 = -70, lon2 = -68, k1 = 1, k2 = 1 } """ if grid is None: raise ValueError("seed.latlon_box: grid is required") lon, lat = _grid_lonlat(grid, gcm) width = (lon2 - lon1) % 360.0 in_box = ( (lat >= lat1) & (lat <= lat2) & ((lon - lon1) % 360.0 <= (360.0 if lon2 - lon1 >= 360.0 else width)) ) if k1 is None: k1 = kpos if k2 is None: k2 = k1 kmt = np.asarray(grid.kmt) ivec, jvec, kvec = [], [], [] for k in range(k1, k2 + 1): ii, jj = np.nonzero(in_box & (kmt >= k)) ivec.append(ii + 1) jvec.append(jj + 1) kvec.append(np.full(ii.shape, k)) ivec, jvec, kvec = (np.concatenate(v) for v in (ivec, jvec, kvec)) if ivec.size == 0: raise ValueError( f"seed.latlon_box: no ocean cells in lat {lat1}..{lat2}, " f"lon {lon1}..{lon2} (levels {k1}..{k2}); the grid spans lat " f"{np.nanmin(lat):.1f}..{np.nanmax(lat):.1f}, lon " f"{np.nanmin(lon):.1f}..{np.nanmax(lon):.1f}" ) return _expand_sub(ivec, jvec, kvec, **sub_kw)
[docs] def latlon_point( lat: float, lon: float, dlat: float, dlon: float | None = None, kpos: int = 1, grid: GridFields | None = None, **kw, ) -> ParticleState: """Seed the ocean cells within *dlat* / *dlon* degrees of a point. A :func:`latlon_box` of ``lat ± dlat`` by ``lon ± dlon`` (*dlon* defaults to *dlat*); other keywords (``k1``, ``k2``, ``gcm``, sub-cell options) are passed on. """ dlon = dlat if dlon is None else dlon return latlon_box( lat - dlat, lat + dlat, lon - dlon, lon + dlon, kpos, grid, **kw )
[docs] def latlon_list( df: pd.DataFrame | str | None = None, grid: GridFields | None = None, *, seed_file: str | None = None, lat: list[float] | None = None, lon: list[float] | None = None, lat_col: str = "lat", lon_col: str = "lon", k_col: str = "k", kpos: int = 1, gcm: str | None = None, **_ignored, ) -> ParticleState: """Seed one particle at each lat/lon position of a table. Each row becomes a particle at the exact fractional grid position of its point (not snapped to the cell centre), found with :func:`tracmass.trajs.coords.lonlat_to_xy` on the adapter's ``lonlat_grid(grid)``. A point on land (or outside the grid) is moved to the centre of the nearest ocean cell. Parameters ---------- df : pd.DataFrame or str or None Table with *lat_col* / *lon_col* columns (and optionally *k_col*, the 1-based level of each point). A str is read as a file, like *seed_file*. grid : GridFields The run grid (required): selects the GCM's lon/lat window (for a ``subset``) and its ``kmt`` masks out land. seed_file : str or None, optional Path to a ``.csv`` or ``.parquet`` file to read *df* from. lat, lon : list of float or None, optional Positions given directly instead of *df* / *seed_file*. lat_col, lon_col, k_col : str, optional Column names. Defaults ``"lat"``, ``"lon"``, ``"k"``. kpos : int, optional 1-based level for every particle when there is no *k_col* column. Default 1. gcm : str or None, optional Adapter providing ``lonlat_grid``. Default: ``[env.gcm] name``. **_ignored Sub-cell options from a settings table are accepted and ignored: each point is seeded exactly once. Returns ------- ParticleState Particles in subset-relative coordinates, in row order (rows whose level lies below the local bottom, ``k > kmt``, are dropped). Raises ------ ValueError If *grid* or the positions are missing, a column is absent, or no point lies in an ocean cell deep enough for its level. Examples -------- >>> df = pd.DataFrame({"lat": [-63.0, -62.5], "lon": [-69.0, -68.2]}) >>> state = seed.latlon_list(df, grid=grid) From settings:: [omi.seed] approach = "latlon_list" seed_file = "stations.csv" # columns lat, lon (and optionally k) or with the positions inline:: [omi.seed] approach = "latlon_list" lat = [-63.0, -62.5] lon = [-69.0, -68.2] """ from tracmass.trajs.coords import lonlat_to_xy if grid is None: raise ValueError("seed.latlon_list: grid is required") if isinstance(df, str): df, seed_file = None, df if df is None and seed_file is not None: df = ( pd.read_parquet(seed_file) if str(seed_file).endswith(".parquet") else pd.read_csv(seed_file) ) if df is None: if lat is None or lon is None: raise ValueError( "seed.latlon_list: give a DataFrame, a seed_file, or lat and lon" ) df = pd.DataFrame({lat_col: np.atleast_1d(lat), lon_col: np.atleast_1d(lon)}) missing = [c for c in (lat_col, lon_col) if c not in df.columns] if missing: raise ValueError( f"seed.latlon_list: missing column(s) {missing}; " f"the table has {list(df.columns)}" ) lat_v = np.asarray(df[lat_col], dtype=float) lon_v = np.asarray(df[lon_col], dtype=float) if k_col in df.columns: k_v = np.asarray(df[k_col], dtype=int) else: k_v = np.full(lat_v.shape, int(kpos)) lon_arr, lat_arr = _grid_lonlat(grid, gcm) kmt = np.asarray(grid.kmt) x, y = lonlat_to_xy(lon_v, lat_v, (lon_arr, lat_arr, kmt)) x, y = np.asarray(x, dtype=float), np.asarray(y, dtype=float) i0, j0 = np.floor(x).astype(int), np.floor(y).astype(int) keep = (k_v >= 1) & (k_v <= kmt[i0, j0]) if not keep.any(): raise ValueError( f"seed.latlon_list: none of the {lat_v.size} points lies in an " f"ocean cell deep enough for its level" ) if not keep.all(): warnings.warn( f"seed.latlon_list: dropped {int((~keep).sum())} of {keep.size} " f"points whose level is below the local bottom (k > kmt)", stacklevel=2, ) # Cell k spans [k-1, k]; place the particle at the level's centre. return _expand_sub( x[keep], y[keep], k_v[keep] - 0.5, sub_dist="already_xyz" )
#: Seeding approaches usable from a ``[env.seed]`` settings table, by name. #: Each takes the table's ``box`` entries and sub-cell keys as keyword #: arguments plus ``grid=``. SETTINGS_APPROACHES = { "ijk": ijk, "latlon_box": latlon_box, "latlon_point": latlon_point, "latlon_list": latlon_list, }
[docs] def mask(mask: np.ndarray, kpos: int = 1, **sub_kw) -> ParticleState: """Seed particles at every True cell of a raw ``(j, i)``-ordered mask. ``mask`` must be in the grid's *native* row-major orientation — the same orientation grid fields come in straight from xarray/NetCDF (e.g. ``seagrid.ecco2.grid_definition().kmt.values`` or ``seagrid.glorys``'s raw ``kmt``): latitude (``j``) first, longitude (``i``) second. This is the *opposite* of ``GridFields.kmt``'s ``(imt, jmt)`` convention — do not pass ``grid.kmt`` here directly; use :func:`ijk` for that. Parameters ---------- mask : np.ndarray Boolean array, shape ``(jmt, imt)`` for a 2-D mask or ``(jmt, imt, kmt)`` for a 3-D one. True marks a cell to seed. kpos : int, optional 1-based vertical level for seeded particles when ``mask`` is 2-D. Ignored (and superseded by the mask's own k-axis) when ``mask`` is 3-D. Default 1. **sub_kw Forwarded to :func:`_expand_sub` (e.g. ``sub_dist``, ``i_sub_num``). Returns ------- ParticleState One particle per True cell, placed at the cell centre, at level ``kpos`` (2-D mask) or the cell's own k (3-D mask). Warns ----- UserWarning If ``mask.shape[0] > mask.shape[1]`` (more j than i). Ocean grids are normally near-global in longitude but span at most the poles in latitude, so ``jmt > imt`` usually means ``mask`` was passed already transposed to ``(imt, jmt)`` instead of the expected raw ``(jmt, imt)``. Examples -------- >>> kmt = seagrid.ecco2.grid_definition().kmt.values # (jmt, imt) >>> state = seed.mask(kmt > 0, kpos=1) """ if mask.shape[0] > mask.shape[1]: warnings.warn( f"seed.mask() expects a raw (jmt, imt) grid (j first), but got " f"shape {mask.shape} with more j than i " f"({mask.shape[0]} > {mask.shape[1]}). Ocean grids are normally " "wider in longitude (i) than latitude (j) -- this may be an " "already-transposed (imt, jmt) array.", stacklevel=2, ) if mask.ndim == 2: jmt, imt = mask.shape jgrid, igrid = np.mgrid[1 : jmt + 1, 1 : imt + 1] ivec, jvec = igrid[mask], jgrid[mask] kvec = np.full(ivec.shape, kpos) else: jmt, imt, kmt = mask.shape jgrid, igrid, kgrid = np.mgrid[1 : jmt + 1, 1 : imt + 1, 1 : kmt + 1] ivec, jvec, kvec = igrid[mask], jgrid[mask], kgrid[mask] return _expand_sub(ivec, jvec, kvec, **sub_kw)
def from_df( df: pd.DataFrame, i_col_name: str = "i", j_col_name: str = "j", k_col_name: str = "k", flux: FluxDataset | None = None, **sub_kw, ) -> ParticleState: ivec, jvec, kvec = df[i_col_name].data, df[j_col_name].data, df[k_col_name].data if flux is not None: mask = flux.kmt.astype(bool).values[ivec, jvec] ivec, jvec, kvec = ivec[mask], jvec[mask], kvec[mask] return _expand_sub(ivec, jvec, kvec, **sub_kw) def from_df_xyz( df: pd.DataFrame, i_col_name: str = "i", j_col_name: str = "j", k_col_name: str = "k", flux: FluxDataset | None = None, **sub_kw, ) -> ParticleState: sub_kw["sub_dist"] = "already_xyz" return from_df( df=df, i_col_name=i_col_name, j_col_name=j_col_name, k_col_name=k_col_name, flux=flux, **sub_kw, ) def _expand_sub( ivec: np.array, jvec: np.array, kvec: np.array, **sub_kw ) -> ParticleState: sub_dist = sub_kw.get("sub_dist", "rect_xyz") if sub_dist == "rect_xyz": def expand(vec1, vec2, vec3, sub_num): subvec = np.linspace(-1, 0, sub_num + 2)[1:-1] vec1 = np.hstack([vec1 + pos for pos in subvec]) vec2 = np.hstack([vec2 for pos in subvec]) vec3 = np.hstack([vec3 for pos in subvec]) return vec1, vec2, vec3 x0, y0, z0 = expand(ivec, jvec, kvec, sub_kw.get("i_sub_num", 1)) y0, z0, x0 = expand(y0, z0, x0, sub_kw.get("j_sub_num", 1)) z0, x0, y0 = expand(z0, x0, y0, sub_kw.get("k_sub_num", 1)) elif sub_dist == "gaus_xyz": mean = [0, 0] cov = [[1, 0], [0, 100]] rng = np.random.default_rng() def norm(vec): vec = vec / vec.max() / 2 vec = vec / np.abs(vec.min()) / 2 return vec[(vec < 0.5) & (vec > -0.5)][:5000] def expand(vec, sub_num): n_part = len(vec) sub_x, sub_y = rng.multivariate_normal(mean, cov, n_part + 20).T sub_x, sub_y = norm(sub_x), norm(sub_y) x0 = expand(ivec, sub_kw.get("tot_sub_num", 1)) y0 = expand(jvec, sub_kw.get("tot_sub_num", 1)) z0 = expand(kvec, sub_kw.get("tot_sub_num", 1)) elif sub_dist == "already_xyz": x0, y0, z0 = ivec, jvec, kvec else: # Cell ia spans [ia-1, ia]; place particle at cell centre ia-0.5. x0, y0, z0 = ivec - 0.5, jvec - 0.5, kvec - 0.5 ia = jnp.floor(x0).astype(jnp.int32) + 1 ja = jnp.floor(y0).astype(jnp.int32) + 1 ka = jnp.floor(z0).astype(jnp.int32) + 1 n_particles = len(ia) state = ParticleState( x=x0, y=y0, z=z0, ia=ia, ja=ja, ka=ka, tt=jnp.zeros(n_particles), ts=jnp.zeros(n_particles), t0=jnp.zeros(n_particles), subvol=jnp.ones(n_particles) * 86400.0, # tseas as subvol for ts tracking niter=jnp.zeros(n_particles, dtype=jnp.int32), active=jnp.ones(n_particles, dtype=jnp.bool_), reason=jnp.zeros(n_particles, dtype=jnp.int16), ) return state """ ! loneparticles IF (loneparticle>0 .AND. log_level>0) THEN PRINT '(A,I7)','WARNING! Loneparticle is set to : ', loneparticle ELSEIF (log_level>0) THEN PRINT '(A,I9)',' Cells used : ', nsdMax END IF SUBROUTINE seed() ! -------------------------------------------------- ! ! Purpose: ! Seeding particles in TRACMASS. ! ! ! Method: ! Populate the position seeding array in trj and nrj. ! ! -------------------------------------------------- REAL(DP) :: vol IF (log_level >= 5) THEN PRINT*,' entering seed ' END IF findTime: DO jsd=1,nsdTim IF (seed_tim(jsd) == ints) THEN itim = seed_tim(jsd) EXIT findTime ELSE IF (seed_tim(jsd) /= ints .AND. jsd == nsdTim) THEN itim = -1 RETURN END IF END DO findTime !Loop over the seed size (nsdMax) startLoop: DO jsd=1,nsdMax IF ( (seedTime == 1 .OR. seedTime == 2) .AND. & & (ints /= itim) ) THEN CYCLE startLoop ELSE IF (seedTime /= 1 .AND. seedTime /= 2) THEN PRINT*,'timeStart =',seedTime,' is not a valid configuration!' STOP END IF iist = seed_ijk(jsd,1) ijst = seed_ijk(jsd,2) ikst = seed_ijk(jsd,3) CALL reverse() isec = seed_set(jsd,1) idir = seed_set(jsd,2) ! Skip the loop IF (iist < 1) CYCLE startLoop IF (ijst < 1) CYCLE startLoop IF (ikst < 1) CYCLE startLoop IF (iist > imt) CYCLE startLoop IF (ijst > jmt) CYCLE startLoop IF (ikst > km) CYCLE startLoop IF (kmt(iist,ijst) == 0) CYCLE startLoop vol = 0. kb = ikst jb = ijst ib = iist ibm = ib - 1 IF (ibm == 0) ibm = imt SELECT CASE(isec) CASE(1) ! Through eastern meridional-vertical surface vol = uflux(iist,ijst,ikst,nsm) CASE (2) ! Through northern zonal-vertical surface vol = vflux(iist,ijst,ikst,nsm) CASE (3) ! Through upper zonal-meridional surface vol = 1. ! Vertical CALL vertvel (ib,ibm,jb,kb) #if defined w_explicit vol = wflux(ib,jb,kb,nsm) #elif w_2dim vol = 1. #else vol = wflux(kb,nsm) #endif END SELECT IF ((nff*idir*vol <= 0.d0 .AND. idir /= 0 ) .OR. (vol == 0.)) CYCLE startLoop ! Volume/mass transport needs to be positive vol = ABS(vol) ! Number of trajectories for box (iist,ijst,ikst) SELECT CASE (nqua) CASE (1) ! partQuant particles per seed gridcell num = INT(partQuant) CASE (2) ! particles reflect mass transport at seeding. ! set by partQuant num = INT(vol/partQuant) IF (MOD(vol,partQuant) /= 0) num = num + 1 ! For num>4, num will be assigned to a square number IF (FLOAT(INT(SQRT(FLOAT(num)))) /= SQRT(FLOAT(num)) .AND. num>4) THEN num = (INT(SQRT(FLOAT(num))) + 1)**2 END IF CASE (3) ! particle reflects air/water mass/volume at seeding vol = dzt(ib,jb,kb,1) num = INT(vol/partQuant) END SELECT IF (num == 0) num = 1 ! Subvol definition IF (num == 1) THEN ijt = 1 ikt = 1 ELSE CALL split_grid() END IF #if w_2dim ! Makes no difference in depth ijt = num; ikt = 1 #endif subvol = vol / DBLE (ijt*ikt) IF (subvol == 0.d0) THEN PRINT *, ' Transport of particle is zero!!!' PRINT *, ' vol :', vol PRINT *, ' uflux :', uflux (ib, jb, kb,nsm) PRINT *, ' vflux :', vflux (ib, jb, kb,nsm) PRINT *, ' subvol :', subvol STOP ENDIF ! Determine start position for each particle ! -------------------------------------------------- ijjLoop: DO jjt=1,ijt kkkLoop: DO jkt=1,ikt IF(x1>IMT .or. x1<0 .or. y1>JMT .or. y1<0 .or. z1>KM .or. z1<0 ) THEN PRINT *, ' Particles outside the domain !!!' PRINT *, ' ib:', ib, 'ibm:',ib-1 PRINT *, ' jb:', jb, ' kb:',kb PRINT *, ' [x1,y1,z1] = ',x1,y1,z1 STOP END IF ! If tracers are activated, initialise only those defined between the limits imposed by tracer0min and tracermax0 IF (l_tracers) THEN DO itrac = 1, numtracers ! Depending on the dimensions ktracer = kb IF (tracers(itrac)%dimension == '2D') ktracer = 1 ! Compute the value of the tracer IF (isec == 1) THEN ! Zonal wall iistp = iist + 1 IF (iist == IMT) iistp = 1 tracervalue(itrac) = 0.5*tracers(itrac)%data(iist,jb,ktracer,nsm) & + 0.5*tracers(itrac)%data(iistp,jb,ktracer,nsm) ELSE IF (isec == 2) THEN ! Meridional wall tracervalue(itrac) = 0.5*tracers(itrac)%data(ib,ijst,ktracer,nsm) & + 0.5*tracers(itrac)%data(ib,ijst+1,ktracer,nsm) ELSE IF (isec == 3) THEN ! Horizontal wall IF (ikst == KM) THEN tracervalue(itrac) = tracers(itrac)%data(ib,jb,ikst,nsm) ELSE tracervalue(itrac) = 0.5*tracers(itrac)%data(ib,jb,ikst,nsm) & + 0.5*tracers(itrac)%data(ib,jb,ikst+1,nsm) END IF END IF tracerbinvalue(itrac,2) = tracerbin(tracervalue(itrac),itrac) IF ((tracervalue(itrac)<tracer0min(itrac)) .OR. (tracervalue(itrac)>tracer0max(itrac))) THEN CYCLE kkkLoop END IF END DO END IF ! Only one particle for diagnistics purposes IF ((loneparticle>0) .and. (ntrac.ne.loneparticle)) THEN trajectories(ntrac)%active = .FALSE. CYCLE kkkLoop END IF ! Rerun/streamfunction options IF ((l_rerun .EQV..TRUE.) .AND. (trajectories(ntrac)%lbas==-1)) THEN trajectories(ntrac)%active = .FALSE. CYCLE kkkLoop END IF ! tt - time, fractions of ints ! ts - time [s] rel to start ts = DBLE (ints-1) tt = ts * tseas tss = 0.d0 ! Boxface IF (isec ==1 .AND. nff*idir>0) boxface = 1 IF (isec ==1 .AND. nff*idir<0) boxface = 2 IF (isec ==2 .AND. nff*idir>0) boxface = 3 IF (isec ==2 .AND. nff*idir<0) boxface = 4 IF (isec ==3 .AND. nff*idir>0) boxface = 5 IF (isec ==3 .AND. nff*idir<0) boxface = 6 !Save initial particle position IF(log_level >= 3) THEN PRINT*,' write initial trajectory position ' END IF CALL write_data('ini') CALL write_data('run') END DO kkkLoop END DO ijjLoop END DO startLoop IF (log_level >= 5) THEN PRINT*,' leaving seed' END IF END SUBROUTINE seed """