Skip to content

Commit 36cc72d

Browse files
Add Custom Merge Kernel
1 parent 7bae9f1 commit 36cc72d

File tree

1 file changed

+73
-19
lines changed

1 file changed

+73
-19
lines changed

docs/user_guide/examples_v3/tutorial_interaction.ipynb

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,7 @@
241241
"source": [
242242
"## Merging particles\n",
243243
"\n",
244-
"Another type of interaction that is supported is the merging of particles. The supported merging functions also comes with limitations (only mutual-nearest particles can be accurately merged), so this is really just a prototype. Nevertheless, the example below shows the possibilities that merging of particles can provide for more complex simulations.\n",
245-
"\n",
246-
"In the example below, we use two build-in Kernels: `NearestNeighborWithinRange` and `MergeWithNearestNeighbor`.\n"
244+
"Another type of interaction is the merging of particles. The merging Kernel also comes with limitations (only mutual-nearest particles can be accurately merged), so this is really just a prototype. Nevertheless, the example below shows the possibilities that merging of particles can provide for more complex simulations."
247245
]
248246
},
249247
{
@@ -253,25 +251,81 @@
253251
"metadata": {},
254252
"outputs": [],
255253
"source": [
256-
"npart = 800\n",
254+
"def Merge(particles, fieldset):\n",
255+
" \"\"\"Kernel that \"merges\" two particles that are within interaction_distance range\n",
256+
" Particles must have a 'mass' Variable, and during merging the mass of the smallest\n",
257+
" particle is transferred to the largest particle. The smallest particle is then deleted.\n",
258+
" \"\"\"\n",
259+
" interaction_distance = 0.05\n",
260+
"\n",
261+
" N = len(particles.lon)\n",
262+
"\n",
263+
" # calculate pairwise distances (n_particles × n_particles)\n",
264+
" dx = particles.lon[None, :] - particles.lon[:, None]\n",
265+
" dy = particles.lat[None, :] - particles.lat[:, None]\n",
266+
" distances = np.sqrt(dx**2 + dy**2)\n",
257267
"\n",
258-
"X = np.random.uniform(-1, 1, size=npart)\n",
259-
"Y = np.random.uniform(-1, 1, size=npart)\n",
268+
" # mask distances by interaction range\n",
269+
" within = (distances > 0) & (distances < interaction_distance)\n",
270+
" dist_masked = np.where(within, distances, np.inf)\n",
271+
"\n",
272+
" # nearest neighbour for each particle (index) and corresponding distance\n",
273+
" nn = np.argmin(dist_masked, axis=1)\n",
274+
" nn_dist = dist_masked[np.arange(N), nn]\n",
275+
" has_nn = np.isfinite(nn_dist)\n",
276+
"\n",
277+
" # mutual nearest: nn[nn[i]] == i\n",
278+
" i_idx = np.arange(N)\n",
279+
" mutual = has_nn & (nn[nn] == i_idx)\n",
280+
"\n",
281+
" # process each mutual pair only once (i < j)\n",
282+
" pair_mask = mutual & (i_idx < nn)\n",
283+
" pair_i = i_idx[pair_mask]\n",
284+
" pair_j = nn[pair_mask]\n",
285+
"\n",
286+
" if pair_i.size == 0:\n",
287+
" return\n",
288+
"\n",
289+
" # larger = where mass_j >= mass_i -> j else i\n",
290+
" mass_i = particles.mass[pair_i]\n",
291+
" mass_j = particles.mass[pair_j]\n",
292+
" larger_idx = np.where(mass_j > mass_i, pair_j, pair_i)\n",
293+
" smaller_idx = np.where(mass_j > mass_i, pair_i, pair_j)\n",
294+
"\n",
295+
" # perform transfer and mark deletions\n",
296+
" # note that we use temporary arrays for indexing because of KernelParticle bug (GH #2143)\n",
297+
" masses = particles.mass\n",
298+
" states = particles.state\n",
299+
"\n",
300+
" # transfer mass from smaller to larger and mark smaller for deletion\n",
301+
" masses[larger_idx] += particles.mass[smaller_idx]\n",
302+
" states[smaller_idx] = parcels.StatusCode.Delete\n",
303+
"\n",
304+
" # TODO use particle variables directly KernelParticle bug (GH #2143) is fixed\n",
305+
" particles.mass = masses\n",
306+
" particles.state = states"
307+
]
308+
},
309+
{
310+
"cell_type": "code",
311+
"execution_count": null,
312+
"id": "10",
313+
"metadata": {},
314+
"outputs": [],
315+
"source": [
316+
"npart = 800\n",
260317
"\n",
261-
"# Create custom InteractionParticle class\n",
262-
"# with extra variables nearest_neighbor and mass\n",
318+
"# Create custom Particle class with extra variable mass\n",
263319
"MergeParticle = parcels.Particle.add_variable(\n",
264-
" [\n",
265-
" parcels.Variable(\"nearest_neighbor\", dtype=np.int64, to_write=False),\n",
266-
" parcels.Variable(\"mass\", initial=1, dtype=np.float32),\n",
267-
" ]\n",
320+
" parcels.Variable(\"mass\", dtype=np.float32)\n",
268321
")\n",
269322
"\n",
270323
"pset = parcels.ParticleSet(\n",
271324
" fieldset=DiffusionFieldSet(),\n",
272325
" pclass=MergeParticle,\n",
273-
" lon=X,\n",
274-
" lat=Y,\n",
326+
" lon=np.random.uniform(-1, 1, size=npart),\n",
327+
" lat=np.random.uniform(-1, 1, size=npart),\n",
328+
" mass=np.random.uniform(0.5, 1.5, size=npart),\n",
275329
")\n",
276330
"\n",
277331
"output_file = parcels.ParticleFile(\n",
@@ -281,7 +335,7 @@
281335
"\n",
282336
"kernels = [\n",
283337
" parcels.kernels.DiffusionUniformKh,\n",
284-
" # Merge, # Add the Merge kernel defined above\n",
338+
" Merge, # Add the Merge kernel defined above\n",
285339
"]\n",
286340
"\n",
287341
"pset.execute(\n",
@@ -295,7 +349,7 @@
295349
{
296350
"cell_type": "code",
297351
"execution_count": null,
298-
"id": "10",
352+
"id": "11",
299353
"metadata": {},
300354
"outputs": [],
301355
"source": [
@@ -305,7 +359,7 @@
305359
" np.nanmin(data_xarray[\"time\"].values),\n",
306360
" np.nanmax(data_xarray[\"time\"].values),\n",
307361
" np.timedelta64(1, \"s\"),\n",
308-
") # timerange in nanoseconds\n",
362+
")\n",
309363
"\n",
310364
"fig = plt.figure(figsize=(4, 4), constrained_layout=True)\n",
311365
"ax = fig.add_subplot()\n",
@@ -350,7 +404,7 @@
350404
],
351405
"metadata": {
352406
"kernelspec": {
353-
"display_name": "Python 3 (ipykernel)",
407+
"display_name": "test-latest",
354408
"language": "python",
355409
"name": "python3"
356410
},
@@ -364,7 +418,7 @@
364418
"name": "python",
365419
"nbconvert_exporter": "python",
366420
"pygments_lexer": "ipython3",
367-
"version": "3.11.6"
421+
"version": "3.11.0"
368422
}
369423
},
370424
"nbformat": 4,

0 commit comments

Comments
 (0)