|
357 | 357 | "plt.tight_layout()\n", |
358 | 358 | "plt.show()" |
359 | 359 | ] |
| 360 | + }, |
| 361 | + { |
| 362 | + "cell_type": "markdown", |
| 363 | + "id": "19", |
| 364 | + "metadata": {}, |
| 365 | + "source": [ |
| 366 | + "## Advecting particles with nest transitions\n", |
| 367 | + "\n", |
| 368 | + "We can now set up a particle advection simulation using the nested grids. We first combine all the Fields into a single FieldSet. \n", |
| 369 | + "\n", |
| 370 | + "We rename the individual Fields by appending the nest index to their names, so that we can easily identify which Field belongs to which nest. We also add the `NestID` Field to the FieldSet (note that Parcels v4 supports combining structured and unstructured Fields into one FieldSet, which is very convenient for this usecase)." |
| 371 | + ] |
| 372 | + }, |
| 373 | + { |
| 374 | + "cell_type": "code", |
| 375 | + "execution_count": null, |
| 376 | + "id": "20", |
| 377 | + "metadata": {}, |
| 378 | + "outputs": [], |
| 379 | + "source": [ |
| 380 | + "fields = [NestID]\n", |
| 381 | + "for i, ds in enumerate(ds_in.values()):\n", |
| 382 | + " # TODO : remove depth dimension when Parcels supports 2D copernicusmarine datasets\n", |
| 383 | + " ds = ds.assign_coords(depth=np.array([0]))\n", |
| 384 | + " ds[\"depth\"].attrs[\"axis\"] = \"Z\"\n", |
| 385 | + "\n", |
| 386 | + " fset = parcels.FieldSet.from_copernicusmarine(ds)\n", |
| 387 | + " for fld in fset.fields.values():\n", |
| 388 | + " fld.name = f\"{fld.name}{i}\"\n", |
| 389 | + " fields.append(fld)\n", |
| 390 | + "fieldset = parcels.FieldSet(fields)" |
| 391 | + ] |
| 392 | + }, |
| 393 | + { |
| 394 | + "cell_type": "markdown", |
| 395 | + "id": "21", |
| 396 | + "metadata": {}, |
| 397 | + "source": [ |
| 398 | + "We then define a custom Advection kernel that advects particles using the appropriate velocity Field based on the `NestID` at the particle's location. Note that for simplicity, we use Eulerian advection here, but in a real-world application you would typically use a higher-order scheme." |
| 399 | + ] |
| 400 | + }, |
| 401 | + { |
| 402 | + "cell_type": "code", |
| 403 | + "execution_count": null, |
| 404 | + "id": "22", |
| 405 | + "metadata": {}, |
| 406 | + "outputs": [], |
| 407 | + "source": [ |
| 408 | + "def AdvectEE_Nests(particles, fieldset):\n", |
| 409 | + " particles.nestID = fieldset.NestID[particles]\n", |
| 410 | + "\n", |
| 411 | + " # TODO because of KernelParticle bug (GH #2143), we need to copy lon/lat/time to local variables\n", |
| 412 | + " time = particles.time\n", |
| 413 | + " z = particles.z\n", |
| 414 | + " lat = particles.lat\n", |
| 415 | + " lon = particles.lon\n", |
| 416 | + " u = np.zeros_like(particles.lon)\n", |
| 417 | + " v = np.zeros_like(particles.lat)\n", |
| 418 | + "\n", |
| 419 | + " unique_ids = np.unique(particles.nestID)\n", |
| 420 | + " for nid in unique_ids:\n", |
| 421 | + " mask = particles.nestID == nid\n", |
| 422 | + " UVField = getattr(fieldset, f\"UV{nid}\")\n", |
| 423 | + " (u[mask], v[mask]) = UVField[time[mask], z[mask], lat[mask], lon[mask]]\n", |
| 424 | + "\n", |
| 425 | + " particles.dlon += u * particles.dt\n", |
| 426 | + " particles.dlat += v * particles.dt\n", |
| 427 | + "\n", |
| 428 | + "\n", |
| 429 | + "pset = parcels.ParticleSet(\n", |
| 430 | + " fieldset, pclass=NestParticle, lon=X.flatten(), lat=Y.flatten()\n", |
| 431 | + ")\n", |
| 432 | + "ofile = parcels.ParticleFile(\"nest_particles.zarr\", outputdt=np.timedelta64(1, \"h\"))\n", |
| 433 | + "pset.execute(\n", |
| 434 | + " AdvectEE_Nests,\n", |
| 435 | + " runtime=np.timedelta64(24, \"h\"),\n", |
| 436 | + " dt=np.timedelta64(1, \"h\"),\n", |
| 437 | + " output_file=ofile,\n", |
| 438 | + ")" |
| 439 | + ] |
| 440 | + }, |
| 441 | + { |
| 442 | + "cell_type": "markdown", |
| 443 | + "id": "23", |
| 444 | + "metadata": {}, |
| 445 | + "source": [ |
| 446 | + "And finally we plot the particles moving through the nested grids, confirming that they correctly transition between the nests based on their location." |
| 447 | + ] |
| 448 | + }, |
| 449 | + { |
| 450 | + "cell_type": "code", |
| 451 | + "execution_count": null, |
| 452 | + "id": "24", |
| 453 | + "metadata": {}, |
| 454 | + "outputs": [], |
| 455 | + "source": [ |
| 456 | + "fig, ax = plt.subplots(1, 1, figsize=(10, 5))\n", |
| 457 | + "\n", |
| 458 | + "ds_out = xr.open_zarr(\"nest_particles.zarr\")\n", |
| 459 | + "\n", |
| 460 | + "cmap = plt.get_cmap(\"viridis\", 3)\n", |
| 461 | + "sc = ax.scatter(ds_out.lon, ds_out.lat, c=ds_out.nestID, s=4, cmap=cmap, vmin=0, vmax=2)\n", |
| 462 | + "\n", |
| 463 | + "cbar = plt.colorbar(sc, ticks=[0, 1, 2], ax=ax)\n", |
| 464 | + "cbar.set_label(\"Nest ID\")\n", |
| 465 | + "ax.set_title(\"Particle advection through nests\")\n", |
| 466 | + "plt.show()" |
| 467 | + ] |
360 | 468 | } |
361 | 469 | ], |
362 | 470 | "metadata": { |
|
0 commit comments