diff --git a/parcels/examples/tutorial_delaystart.ipynb b/parcels/examples/tutorial_delaystart.ipynb index 4d180b668..61831e7b5 100644 --- a/parcels/examples/tutorial_delaystart.ipynb +++ b/parcels/examples/tutorial_delaystart.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In many applications, it is needed to 'delay' the start of particle advection. For example because particles need to be released at different times throughout an experiment. Or because particles need to be released at a conatant rate from the same set of locations.\n", + "In many applications, it is needed to 'delay' the start of particle advection. For example because particles need to be released at different times throughout an experiment. Or because particles need to be released at a constant rate from the same set of locations.\n", "\n", "This tutorial will show how this can be done. We start with importing the relevant modules." ] @@ -26,6 +26,7 @@ "from parcels import FieldSet, ParticleSet, JITParticle, plotTrajectoriesFile\n", "from parcels import AdvectionRK4\n", "import numpy as np\n", + "import xarray as xr\n", "from datetime import timedelta as delta" ] }, @@ -96,8 +97,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO: Compiled JITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/5fba5501ea36ea205605260b5254062f.so\n", - "100% (86400.0 of 86400.0) |##############| Elapsed Time: 0:00:00 Time: 0:00:00\n" + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/lib15ab640308c954e580b6f93be742baae_0.so\n" ] } ], @@ -596,8 +596,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO: Compiled JITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/694933c823154d73a884aab64a89b625.so\n", - "100% (86400.0 of 86400.0) |##############| Elapsed Time: 0:00:02 Time: 0:00:02\n" + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/libcbcec39cf73cfa7671e8798abaac733b_0.so\n" ] } ], @@ -1183,9 +1182,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "INFO: Compiled JITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/aca27122514704372f3f6e56d1ca8af6.so\n", - "100% (28800.0 of 28800.0) |##############| Elapsed Time: 0:00:00 Time: 0:00:00\n", - "100% (54000.0 of 54000.0) |##############| Elapsed Time: 0:00:00 Time: 0:00:00\n" + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/libd1c17d6554e6d39a5c989beb50bcc2d3_0.so\n" ] }, { @@ -1724,6 +1721,137 @@ "plotTrajectoriesFile('DelayParticle_releasedt_9hrs.nc', mode='movie2d_notebook')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Synced `time` in the output file\n", + "\n", + "Note that, because the `outputdt` variable controls the JIT-loop, all particles are written _at the same time_, even when they start at a non-multiple of `outputdt`. \n", + "\n", + "For example, if your particles start at `time=[0, 1, 2]` and `outputdt=2`, then the times written (for `dt=1` and `endtime=4`) will be " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 2 4]\n", + " [ 2 4 'NaT']\n", + " [ 2 4 'NaT']]\n" + ] + } + ], + "source": [ + "outtime_expected = np.array([[0, 2, 4], [2, 4, np.datetime64(\"NaT\")], [2, 4, np.datetime64(\"NaT\")]], dtype=\"timedelta64[s]\")\n", + "print(outtime_expected)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/lib73f1058452aa339434e56df780d0ba46_0.so\n" + ] + } + ], + "source": [ + "outfilepath = \"DelayParticle_nonmatchingtime.nc\"\n", + "\n", + "pset = ParticleSet(fieldset=fieldset, pclass=JITParticle,\n", + " lat=[3e3]*3, lon=[3e3]*3, time=[0, 1, 2])\n", + "pset.execute(AdvectionRK4, endtime=4, dt=1, output_file=pset.ParticleFile(name=outfilepath, outputdt=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And indeed, the `time` values in the NetCDF output file are as expected" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 2 4]\n", + " [ 2 4 'NaT']\n", + " [ 2 4 'NaT']]\n" + ] + } + ], + "source": [ + "outtime_infile = xr.open_dataset(outfilepath).time.values[:]\n", + "print(outtime_infile.astype('timedelta64[s]'))\n", + "\n", + "assert (outtime_expected[np.isfinite(outtime_expected)] == outtime_infile[np.isfinite(outtime_infile)]).all()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, for some applications, this behavior may be undesirable; for example when particles need to be analyzed at a same age (instead of at a same time). In that case, we recommend either changing `outputdt` so that it is a common divisor of all start times; or doing multiple Parcels runs with subsets of the original `ParticleSet` (e.g., in the example above, one run with the Particles that start at `time=[0, 2]` and one with the Particle at `time=[1]`). In that case, you will get two files:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/lib4a9283c7ab81279666f072b74de9d644_0.so\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0 2 4]\n", + " [ 2 4 'NaT']]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Compiled ArrayJITParticleAdvectionRK4 ==> /var/folders/r2/8593q8z93kd7t4j9kbb_f7p00000gr/T/parcels-504/libef5ef37165aa5febcc2e916a89504269_0.so\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[1 3 4]]\n" + ] + } + ], + "source": [ + "for times in [[0,2], [1]]:\n", + " pset = ParticleSet(fieldset=fieldset, pclass=JITParticle,\n", + " lat=[3e3]*len(times), lon=[3e3]*len(times), time=times)\n", + " pset.execute(AdvectionRK4, endtime=4, dt=1, output_file=pset.ParticleFile(name=outfilepath, outputdt=2))\n", + " print(xr.open_dataset(outfilepath).time.values[:].astype('timedelta64[s]'))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1748,7 +1876,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.8.10" } }, "nbformat": 4, diff --git a/tests/test_particle_sets.py b/tests/test_particle_sets.py index 043388ed5..3c65b2869 100644 --- a/tests/test_particle_sets.py +++ b/tests/test_particle_sets.py @@ -168,6 +168,19 @@ def test_pset_create_with_time(fieldset, pset_mode, mode, npart=100): assert np.allclose([p.time for p in pset], time, rtol=1e-12) +@pytest.mark.parametrize('pset_mode', pset_modes) +@pytest.mark.parametrize('mode', ['scipy', 'jit']) +def test_pset_not_multipldt_time(fieldset, pset_mode, mode): + times = [0, 1.1] + pset = pset_type[pset_mode]['pset'](fieldset, lon=[0]*2, lat=[0]*2, pclass=ptype[mode], time=times) + + def Addlon(particle, fieldset, time): + particle.lon += particle.dt + + pset.execute(Addlon, dt=1, runtime=2) + assert np.allclose([p.lon for p in pset], [2 - t for t in times]) + + @pytest.mark.parametrize('pset_mode', pset_modes) @pytest.mark.parametrize('mode', ['scipy', 'jit']) def test_pset_repeated_release(fieldset, pset_mode, mode, npart=10):