EM-APEX floats in TIWs#

Test out deployment strategies using OceanParcels

Todo:

  • check that grid is right given Scott’s subsampling

  • slow down particles 10m below surface and 10m above maxdepth

%matplotlib inline
%load_ext watermark

from datetime import timedelta

import cf_xarray
import dask
import dcpy
import distributed
import matplotlib as mpl
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import matplotlib.units as munits
import numpy as np
import pandas as pd
import parcels
import xarray as xr
from parcels import (
    AdvectionRK4,
    ErrorCode,
    FieldSet,
    JITParticle,
    ParticleSet,
    StateCode,
    Variable,
)

import pump

%watermark -iv
INFO: Compiled ParcelsRandom ==> /glade/scratch/dcherian/tmp/parcels-25721/libparcels_random_e8684722-16b7-49c5-ab13-5ee6e4cc1801.so
pandas     : 1.1.3
xarray     : 0.16.3.dev150+g37522e991
dcpy       : 0.1
numpy      : 1.19.2
parcels    : 2.2.2
distributed: 2.30.0
pump       : 0.1
matplotlib : 3.3.2
cf_xarray  : 0.4.1.dev31+g7a8c620
dask       : 2.30.0
ds = xr.open_dataset(
    "/glade/campaign/cgd/oce/people/dcherian/TPOS_MITgcm_1_hb/full_domain/HOLD_NC/Day_2000.nc"
)
dirname = "/glade/campaign/cgd/oce/people/dcherian/TPOS_MITgcm_1_hb/tiw"
filenames = dict.fromkeys(("U", "V", "temp", "salt"), f"{dirname}/File*buoy.nc")

dirname = (
    "/glade/campaign/cgd/oce/people/dcherian/TPOS_MITgcm_1_hb/full_domain/HOLD_NC/"
)
filenames = dict.fromkeys(("U", "V"), f"{dirname}/Day_[0-9][0-9][0-9][0-9].nc")


variables = {
    "U": "u",
    "V": "v",
    # "temp": "theta", "salt": "salt"
}

dimensions = {"lat": "latitude", "lon": "longitude", "depth": "depth", "time": "time"}

# from_netcdf assumes A-grid
# from _mitgcm specialized from_c_grid_dataset to MITgcm conventions
# I think it's handling the "inner"/"outer"/"left"/"right" stuff from xgcm
# https://nbviewer.jupyter.org/github/OceanParcels/parcels/blob/master/parcels/examples/documentation_indexing.ipynb
fieldset = FieldSet.from_mitgcm(
    filenames,
    variables,
    dimensions,
    chunksize={
        "depth": ("depth", 150),
        "lat": ("latitude", 60),
        "lon": ("longitude", 250),
    },
)
fieldset.mindepth = 0  # fieldset.U.depth[0]  # uppermost layer in the hydrodynamic data
# Define the new Kernel that mimics Argo vertical movement
def EMApexVerticalMovement(particle, fieldset, time):
    # if all(fieldset.U.depth >= 0):
    #    sign = 1
    # else:
    #    sign = -1

    sign = -1  # vertical coord is negative; positive upward

    # driftdepth = 100  # maximum depth in m
    maxdepth = 100  # maximum depth in m
    vertical_speed = 0.15  # sink and rise speed in m/s

    surfacetime = 10 * 60  # time spent at surface
    mindepth = 0.6  # TODO: why
    # drifttime = 10 * 86400  # time of deep drift in seconds

    # debugstr = f"in: phase: {particle.cycle_phase}, depth={particle.depth} || "
    if particle.cycle_phase == 0:
        # Phase 0: Sinking with vertical_speed until depth is driftdepth
        particle.depth += sign * vertical_speed * particle.dt
        if abs(particle.depth) >= maxdepth:
            particle.cycle_phase = 1

    elif particle.cycle_phase == 1:
        # Phase 3: Rising with vertical_speed until at surface
        particle.depth -= sign * vertical_speed * particle.dt

        # Sample fields
        # particle.temp = fieldset.temp[particle]
        # particle.salt = fieldset.salt[particle]

        if abs(particle.depth) <= mindepth:
            particle.depth = sign * mindepth
            # particle.temp = 0./0.  # reset temperature to NaN at end of sampling cycle
            particle.cycle_phase = 4

    elif particle.cycle_phase == 4:
        # Phase 4: Transmitting at surface until cycletime is reached
        particle.transmit_age += particle.dt
        if particle.transmit_age > surfacetime:
            particle.cycle_phase = 0
            particle.transmit_age = 0

    # if particle.state == StateCode.Evaluate:
    #    particle.cycle_age += particle.dt  # update cycle_age

    # debugstr += f", final: phase: {particle.cycle_phase}, depth = {particle.depth}, state: {particle.state}"
    # print(debugstr)


# Define a new Particle type including extra Variables
class EMApexParticle(parcels.JITParticle):
    # Phase of cycle: init_descend=0, drift=1, profile_descend=2, profile_ascend=3, transmit=4
    cycle_phase = Variable("cycle_phase", dtype=np.int32, initial=0.0)
    transmit_age = Variable("transmit_age", dtype=np.float32, initial=0.0)
    # drift_age = Variable('drift_age', dtype=np.float32, initial=0.)

    # temp = Variable("temp", dtype=np.float32, initial=np.nan)
    # salt = Variable("salt", dtype=np.float32, initial=np.nan)


lats = np.arange(-3, 3.01, 0.05)
lons = -140 * np.ones_like(lats)
depths = -0.5 * np.ones_like(lats)

pset = ParticleSet(
    fieldset=fieldset,
    pclass=EMApexParticle,
    lon=lons,
    lat=lats,
    depth=depths,
)

# combine Argo vertical movement kernel with built-in Advection kernel
kernels = EMApexVerticalMovement + pset.Kernel(parcels.AdvectionRK4)

# Create a ParticleFile object to store the output
output_file = pset.ParticleFile(
    name="../trajectories/emapex_float", outputdt=timedelta(hours=1)
)

# Now execute the kernels for 30 days, saving data every 30 minutes
pset.execute(
    kernels,
    runtime=timedelta(days=180),
    dt=timedelta(minutes=3),
    output_file=output_file,
)

output_file.export()  # export the trajectory data to a netcdf file
Exception ignored in: <function ParticleFile.__del__ at 0x2aec51d5cb80>
Traceback (most recent call last):
  File "/glade/u/home/dcherian/miniconda3/envs/dcpy/lib/python3.8/site-packages/parcels/particlefile.py", line 189, in __del__
    self.close()
  File "/glade/u/home/dcherian/miniconda3/envs/dcpy/lib/python3.8/site-packages/parcels/particlefile.py", line 194, in close
    self.export()
  File "/glade/u/home/dcherian/miniconda3/envs/dcpy/lib/python3.8/site-packages/parcels/particlefile.py", line 305, in export
    pset_info_local = np.load(os.path.join(tempwritedir, 'pset_info.npy'), allow_pickle=True).item()
  File "/glade/u/home/dcherian/miniconda3/envs/dcpy/lib/python3.8/site-packages/numpy/lib/npyio.py", line 416, in load
    fid = stack.enter_context(open(os_fspath(file), "rb"))
FileNotFoundError: [Errno 2] No such file or directory: '../trajectories/out-WRLLBNRV/0/pset_info.npy'
INFO: Compiled EMApexParticleEMApexVerticalMovementAdvectionRK4 ==> /glade/scratch/dcherian/tmp/parcels-25721/c9efc00314d248f2c0e7effb3ee2b30e_0.so
INFO: Temporary output files are stored in ../trajectories/out-SVIYIQQW.
INFO: You can use "parcels_convert_npydir_to_netcdf ../trajectories/out-SVIYIQQW" to convert these to a NetCDF file during the run.
 36% (5734800.0 of 15552000.0) |###      | Elapsed Time: 1:27:13 ETA:  20:49:00
---------------------------------------------------------------------------
TimeExtrapolationError                    Traceback (most recent call last)
<ipython-input-22-15e6ec7f43a4> in <module>
     83 
     84 # Now execute the kernels for 30 days, saving data every 30 minutes
---> 85 pset.execute(
     86     kernels,
     87     runtime=timedelta(days=180),

~/miniconda3/envs/dcpy/lib/python3.8/site-packages/parcels/particlesets/baseparticleset.py in execute(self, pyfunc, endtime, runtime, dt, moviedt, recovery, output_file, movie_background_field, verbose_progress, postIterationCallbacks, callbackdt)
    472                 next_callback += callbackdt * np.sign(dt)
    473             if time != endtime:
--> 474                 next_input = self.fieldset.computeTimeChunk(time, dt)
    475             if dt == 0:
    476                 break

~/miniconda3/envs/dcpy/lib/python3.8/site-packages/parcels/fieldset.py in computeTimeChunk(self, time, dt)
   1035                 nextTime_loc = f.grid.computeTimeChunk(f, time, signdt)
   1036                 if time == nextTime_loc and signdt != 0:
-> 1037                     raise TimeExtrapolationError(time, field=f, msg='In fset.computeTimeChunk')
   1038             nextTime = min(nextTime, nextTime_loc) if signdt >= 0 else max(nextTime, nextTime_loc)
   1039 

TimeExtrapolationError: U sampled outside time domain at time 1995-12-09T16:00:00.000000000.In fset.computeTimeChunk Try setting allow_time_extrapolation to True
output_file.export()
traj.plot.scatter(x="lon", y="lat", hue="ilat", s=2)
<matplotlib.collections.PathCollection at 0x2aeefac29490>
_images/5a845fabf022fccc5ee4babec784732a9596cf598ab757362f242df03a30b6c3.png
traj = xr.open_dataset("../trajectories/emapex_float.nc").set_coords("time")
traj.coords["ilat"] = traj.lat.sel(obs=0)


fig = plt.figure(figsize=(13, 10))
ax = plt.axes(projection="3d")
cb = traj.plot.scatter(x="lon", y="lat", hue="z", s=20, marker="o")
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
ax.set_zlabel("Depth (m)")
plt.show()
_images/b8359a66e0a3baf63d14c672b42b82f633deb6069a1a0671e01f598e150fd23d.png
# Define the new Kernel that mimics Argo vertical movement
def ArgoVerticalMovement(particle, fieldset, time):
    if all(fieldset.U.depth >= 0):
        sign = 1
    else:
        sign = -1

    driftdepth = 1000  # maximum depth in m
    maxdepth = 2000  # maximum depth in m
    vertical_speed = 0.10  # sink and xrise speed in m/s
    cycletime = 10 * 86400  # total time of cycle in seconds
    drifttime = 9 * 86400  # time of deep drift in seconds

    if particle.cycle_phase == 0:
        # Phase 0: Sinking with vertical_speed until depth is driftdepth
        particle.depth += sign * vertical_speed * particle.dt
        print(particle.depth)
        if abs(particle.depth) >= driftdepth:
            particle.cycle_phase = 1

    elif particle.cycle_phase == 1:
        # Phase 1: Drifting at depth for drifttime seconds
        particle.drift_age += particle.dt
        if particle.drift_age >= drifttime:
            particle.drift_age = 0  # reset drift_age for next cycle
            particle.cycle_phase = 2

    elif particle.cycle_phase == 2:
        # Phase 2: Sinking further to maxdepth
        particle.depth += sign * vertical_speed * particle.dt
        if abs(particle.depth) >= maxdepth:
            particle.cycle_phase = 3

    elif particle.cycle_phase == 3:
        # Phase 3: Rising with vertical_speed until at surface
        particle.depth -= sign * vertical_speed * particle.dt
        # particle.temp = fieldset.temp[time, particle.depth, particle.lat, particle.lon]  # if fieldset has temperature
        if abs(particle.depth) <= fieldset.mindepth:
            particle.depth = sign * fieldset.mindepth
            # particle.temp = 0./0.  # reset temperature to NaN at end of sampling cycle
            particle.cycle_phase = 4

    elif particle.cycle_phase == 4:
        # Phase 4: Transmitting at surface until cycletime is reached
        if particle.cycle_age > cycletime:
            particle.cycle_phase = 0
            particle.cycle_age = 0

    # if particle.state == ErrorCode.Evaluate:
    #    particle.cycle_age += particle.dt  # update cycle_age