Skip to content

Commit 89b75bd

Browse files
authored
Merge pull request #76 from Parcels-code/optimise_kernels
Optimise and add new kernels
2 parents 3218321 + 184f3e9 commit 89b75bd

File tree

1 file changed

+174
-23
lines changed

1 file changed

+174
-23
lines changed

plasticparcels/kernels.py

Lines changed: 174 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ def StokesDrift(particle, fieldset, time):
3939
[1] Breivik (2016) - https://doi.org/10.1016/j.ocemod.2016.01.005
4040
4141
"""
42-
# Sample the U / V components of Stokes drift
43-
stokes_U = fieldset.Stokes_U[time, particle.depth, particle.lat, particle.lon]
44-
stokes_V = fieldset.Stokes_V[time, particle.depth, particle.lat, particle.lon]
45-
4642
# Sample the peak wave period
4743
T_p = fieldset.wave_Tp[time, particle.depth, particle.lat, particle.lon]
4844

@@ -51,6 +47,10 @@ def StokesDrift(particle, fieldset, time):
5147

5248
# Only compute displacements if the peak wave period is large enough and the particle is in the water
5349
if T_p > 1E-14 and particle.depth < local_bathymetry:
50+
# Sample the U / V components of Stokes drift
51+
stokes_U = fieldset.Stokes_U[time, particle.depth, particle.lat, particle.lon]
52+
stokes_V = fieldset.Stokes_V[time, particle.depth, particle.lat, particle.lon]
53+
5454
# Peak wave frequency
5555
omega_p = 2. * math.pi / T_p
5656

@@ -102,13 +102,13 @@ def WindageDrift(particle, fieldset, time):
102102
(ocean_U, ocean_V) = fieldset.UV[particle]
103103
ocean_speed = math.sqrt(ocean_U**2 + ocean_V**2)
104104

105-
# Sample the U / V components of wind
106-
wind_U = fieldset.Wind_U[time, particle.depth, particle.lat, particle.lon]
107-
wind_V = fieldset.Wind_V[time, particle.depth, particle.lat, particle.lon]
108-
109105
# Apply windage to particles that have some exposed surface above the ocean surface
110106
# Use a basic approach to only apply windage to particle in the ocean
111-
if particle.depth < 0.5*particle.plastic_diameter and ocean_speed > 1E-12:
107+
if particle.depth < 0.5*particle.plastic_diameter and ocean_speed > 1E-14:
108+
# Sample the U / V components of wind
109+
wind_U = fieldset.Wind_U[time, particle.depth, particle.lat, particle.lon]
110+
wind_V = fieldset.Wind_V[time, particle.depth, particle.lat, particle.lon]
111+
112112
# Compute particle displacement
113113
particle_dlon += particle.wind_coefficient * (wind_U - ocean_U) * particle.dt # noqa
114114
particle_dlat += particle.wind_coefficient * (wind_V - ocean_V) * particle.dt # noqa
@@ -161,15 +161,15 @@ def SettlingVelocity(particle, fieldset, time):
161161
g = fieldset.G # gravitational acceleration [m s-2]
162162
seawater_density = particle.seawater_density # [kg m-3]
163163
temperature = fieldset.conservative_temperature[time, particle.depth, particle.lat, particle.lon]
164-
seawater_salinity = fieldset.absolute_salinity[time, particle.depth, particle.lat, particle.lon] / 1000
164+
seawater_salinity = fieldset.absolute_salinity[time, particle.depth, particle.lat, particle.lon] / 1000.
165165
particle_diameter = particle.plastic_diameter
166166
particle_density = particle.plastic_density
167167

168168
# Compute the kinematic viscosity of seawater
169169
water_dynamic_viscosity = 4.2844E-5 + (1. / ((0.156 * (temperature + 64.993) ** 2) - 91.296)) # Eq. (26) from [2]
170-
A = 1.541 + 1.998E-2 * temperature - 9.52E-5 * temperature ** 2 # Eq. (28) from [2]
171-
B = 7.974 - 7.561E-2 * temperature + 4.724E-4 * temperature ** 2 # Eq. (29) from [2]
172-
seawater_dynamic_viscosity = water_dynamic_viscosity * (1. + A * seawater_salinity + B * seawater_salinity ** 2) # Eq. (27) from [2]
170+
A = 1.541 + 1.998E-2 * temperature - 9.52E-5 * temperature ** 2. # Eq. (28) from [2]
171+
B = 7.974 - 7.561E-2 * temperature + 4.724E-4 * temperature ** 2. # Eq. (29) from [2]
172+
seawater_dynamic_viscosity = water_dynamic_viscosity * (1. + A * seawater_salinity + B * seawater_salinity ** 2.) # Eq. (27) from [2]
173173
seawater_kinematic_viscosity = seawater_dynamic_viscosity / seawater_density # Eq. (25) from [2]
174174

175175
# Compute the density difference of the particle
@@ -179,7 +179,7 @@ def SettlingVelocity(particle, fieldset, time):
179179
dimensionless_diameter = (math.fabs(particle_density - seawater_density) * g * particle_diameter ** 3.) / (seawater_density * seawater_kinematic_viscosity ** 2.) # [-]
180180

181181
# Compute the dimensionless settling velocity w_*
182-
if dimensionless_diameter > 5E9: # "The boundary layer around the sphere becomes fully turbulent, causing a reduction in drag and an increase in settling velocity" - [1]
182+
if dimensionless_diameter > 5.0E9: # "The boundary layer around the sphere becomes fully turbulent, causing a reduction in drag and an increase in settling velocity" - [1]
183183
dimensionless_velocity = 265000. # Set a maximum dimensionless settling velocity
184184
elif dimensionless_diameter < 0.05: # "At values of D_* less than 0.05, (9) deviates signficantly ... from Stokes' law and (8) should be used." - [1]
185185
dimensionless_velocity = (dimensionless_diameter ** 2.) / 5832. # Using Eq. (8) in [1]
@@ -271,9 +271,9 @@ def Biofouling(particle, fieldset, time):
271271
initial_settling_velocity = particle.settling_velocity # settling velocity [m s-1]
272272

273273
# Compute the seawater dynamic viscosity and kinematic viscosity
274-
water_dynamic_viscosity = 4.2844E-5 + (1. / ((0.156 * (temperature + 64.993) ** 2) - 91.296)) # Eq. (26) from [2]
275-
A = 1.541 + 1.998E-2 * temperature - 9.52E-5 * temperature ** 2 # Eq. (28) from [2]
276-
B = 7.974 - 7.561E-2 * temperature + 4.724E-4 * temperature ** 2 # Eq. (29) from [2]
274+
water_dynamic_viscosity = 4.2844E-5 + (1. / ((0.156 * (temperature + 64.993) ** 2.) - 91.296)) # Eq. (26) from [2]
275+
A = 1.541 + 1.998E-2 * temperature - 9.52E-5 * temperature ** 2. # Eq. (28) from [2]
276+
B = 7.974 - 7.561E-2 * temperature + 4.724E-4 * temperature ** 2. # Eq. (29) from [2]
277277
seawater_dynamic_viscosity = water_dynamic_viscosity * (1. + A * seawater_salinity + B * seawater_salinity ** 2) # Eq. (27) from [2]
278278
seawater_kinematic_viscosity = seawater_dynamic_viscosity / particle.seawater_density # Eq. (25) from [2]
279279

@@ -282,7 +282,7 @@ def Biofouling(particle, fieldset, time):
282282
mol_concentration_diatoms = fieldset.bio_diatom[time, particle.depth, particle.lat, particle.lon] # Mole concentration of Diatoms expressed as carbon in sea water
283283
mol_concentration_nanophytoplankton = fieldset.bio_nanophy[time, particle.depth, particle.lat, particle.lon] # Mole concentration of Nanophytoplankton expressed as carbon in seawater
284284
total_primary_production_of_phyto = fieldset.pp_phyto[time, particle.depth, particle.lat, particle.lon] # mg C /m3/day #pp_phyto_
285-
median_mg_carbon_per_cell = 2726e-9 # Median mg of Carbon per cell selected from [3]. From [2] pg7966 - "The conversion from carbon to algae cells is highly variable, ranging between 35339 to 47.76 pg per carbon cell [3]. We choose the median value, 2726 x 10^-9 mg per carbon cell."
285+
median_mg_carbon_per_cell = 2726.0E-9 # Median mg of Carbon per cell selected from [3]. From [2] pg7966 - "The conversion from carbon to algae cells is highly variable, ranging between 35339 to 47.76 pg per carbon cell [3]. We choose the median value, 2726 x 10^-9 mg per carbon cell."
286286
# carbon_molecular_weight = fieldset.carbon_molecular_weight # grams C per mol of C #Wt_C
287287

288288
# Compute concentration numbers
@@ -350,7 +350,7 @@ def Biofouling(particle, fieldset, time):
350350
dimensionless_diameter = (math.fabs(total_density - particle.seawater_density) * fieldset.G * particle_diameter ** 3.) / (particle.seawater_density * seawater_kinematic_viscosity ** 2.) # [-]
351351

352352
# Compute the dimensionless settling velocity w_*
353-
if dimensionless_diameter > 5E9: # "The boundary layer around the sphere becomes fully turbulent, causing a reduction in drag and an increase in settling velocity" - [1]
353+
if dimensionless_diameter > 5.0E9: # "The boundary layer around the sphere becomes fully turbulent, causing a reduction in drag and an increase in settling velocity" - [1]
354354
dimensionless_velocity = 265000. # Set a maximum dimensionless settling velocity
355355
elif dimensionless_diameter < 0.05: # "At values of D_* less than 0.05, (9) deviates signficantly ... from Stokes' law and (8) should be used." - [1]
356356
dimensionless_velocity = (dimensionless_diameter ** 2.) / 5832. # Using Eq. (8) in [1]
@@ -489,7 +489,7 @@ def VerticalMixing(particle, fieldset, time):
489489
490490
Description
491491
----------
492-
A simple verticle mixing kernel that uses a markov-0 process to determine the vertical
492+
A simple vertical mixing kernel that uses a markov-0 process to determine the vertical
493493
displacement of a particle [1]. The deterministic component is determined
494494
using forward-difference with a given `delta_z`.
495495
@@ -524,7 +524,6 @@ def VerticalMixing(particle, fieldset, time):
524524

525525
# Compute the random walk component of Eq. (1)
526526
dz_random = ParcelsRandom.uniform(-1., 1.) * math.sqrt(math.fabs(particle.dt) * 3) * math.sqrt(2 * kz)
527-
# TODO - implement the reflective boundary condition
528527

529528
# Compute rise velocity component of Eq. (1) - Already accounted for in other kernels
530529
dz_wb = 0 # particle.settling_velocity * particle.dt
@@ -535,7 +534,32 @@ def VerticalMixing(particle, fieldset, time):
535534
# Update particle position
536535
particle_ddepth += ddepth # noqa
537536

538-
# Biofouling related kernels
537+
538+
def reflectAtSurface(particle, fieldset, time):
539+
"""A reflecting boundary condition kernel at the ocean surface.
540+
541+
Description
542+
----------
543+
A simple kernel to reflect particles at the ocean surface if they go through the surface.
544+
545+
Parameter Requirements
546+
----------
547+
None
548+
549+
Kernel Requirements
550+
----------
551+
Order of Operations:
552+
This kernel should be performed after all vertical displacement kernels have been applied.
553+
554+
References
555+
----------
556+
None
557+
558+
"""
559+
potential_depth = particle.depth + particle_ddepth # noqa
560+
if potential_depth < 0.:# Particle is above the surface
561+
particle.depth = -potential_depth
562+
particle_ddepth = 0. # noqa
539563

540564

541565
def unbeaching(particle, fieldset, time):
@@ -560,7 +584,7 @@ def unbeaching(particle, fieldset, time):
560584
unbeaching field using the updated particle position.
561585
"""
562586
# Measure the velocity field at the final particle location
563-
(vel_u, vel_v, vel_w) = fieldset.UVW[time, particle.depth + particle_ddepth, particle.lat + particle_dlat, particle.lon + particle_dlon] # noqa
587+
(vel_u, vel_v) = fieldset.UV[time, particle.depth + particle_ddepth, particle.lat + particle_dlat, particle.lon + particle_dlon] # noqa
564588

565589
if math.fabs(vel_u) < 1e-14 and math.fabs(vel_v) < 1e-14:
566590
U_ub = fieldset.unbeach_U[time, particle.depth + particle_ddepth, particle.lat + particle_dlat, particle.lon + particle_dlon] # noqa
@@ -573,6 +597,104 @@ def unbeaching(particle, fieldset, time):
573597
particle_dlat += dlat # noqa
574598

575599

600+
def unbeachingBySamplingAfterwards(particle, fieldset, time):
601+
"""Alternative unbeaching kernel.
602+
603+
Description
604+
----------
605+
A kernel to 'unbeach' particles that have been advected onto non-ocean grid cells.
606+
This kernel samples the velocity field in the four cardinal directions around the particle,
607+
and moves the particle in the direction of the highest velocity magnitude, assuming that
608+
this direction is the ocean direction. This kernel only acts if the particle displacement
609+
in both zonal and meridional directions is (near) zero, indicating that the particle is beached.
610+
611+
Parameter Requirements
612+
----------
613+
Fieldset:
614+
None
615+
616+
Kernel Requirements
617+
----------
618+
Order of Operations:
619+
This kernel should be performed after all other movement kernels.
620+
"""
621+
# In the case of being beached, these displacement will be zero
622+
new_lon = particle.lon + particle_dlon # noqa
623+
new_lat = particle.lat + particle_dlat # noqa
624+
new_depth = particle.depth + particle_ddepth # noqa
625+
626+
if math.fabs(particle_dlon) + math.fabs(particle_dlat) < 1e-14: # noqa
627+
displacement = 1./8. # Degree displacement to sample the velocity field
628+
629+
# Convert 1m/s to degrees/s at the particle latitude in zonal and meridional directions
630+
unbeach_U = 1. / (1852. * 60. * math.cos(particle.lat * math.pi / 180.))
631+
unbeach_V = 1. / (1852. * 60.)
632+
633+
# Sample the velocity field in the four cardinal directions
634+
(U_left, V_left) = fieldset.UV[time, new_depth, new_lat, new_lon - displacement]
635+
(U_right, V_right) = fieldset.UV[time, new_depth, new_lat, new_lon + displacement]
636+
(U_up, V_up) = fieldset.UV[time, new_depth, new_lat + displacement, new_lon]
637+
(U_down, V_down) = fieldset.UV[time, new_depth, new_lat - displacement, new_lon]
638+
639+
# Find the direction of the highest velocity
640+
left = math.sqrt(U_left**2 + V_left**2)
641+
right = math.sqrt(U_right**2 + V_right**2)
642+
up = math.sqrt(U_up**2 + V_up**2)
643+
down = math.sqrt(U_down**2 + V_down**2)
644+
645+
max_vel = 0.
646+
U_dir = 0.
647+
V_dir = 0.
648+
649+
if left + right + up + down > 1e-14:
650+
max_vel = left
651+
U_dir = -1.
652+
V_dir = 0.
653+
if max_vel < right:
654+
max_vel = right
655+
U_dir = 1.
656+
V_dir = 0.
657+
if max_vel < up:
658+
max_vel = up
659+
U_dir = 0.
660+
V_dir = 1.
661+
if max_vel < down:
662+
max_vel = down
663+
U_dir = 0.
664+
V_dir = -1.
665+
666+
# If all four cardinal directions are zero, check diagonal directions
667+
else:
668+
(U_left_up, V_left_up) = fieldset.UV[time, new_depth, new_lat + displacement, new_lon - displacement]
669+
(U_left_down, V_left_down) = fieldset.UV[time, new_depth, new_lat - displacement, new_lon - displacement]
670+
(U_right_up, V_right_up) = fieldset.UV[time, new_depth, new_lat + displacement, new_lon + displacement]
671+
(U_right_down, V_right_down) = fieldset.UV[time, new_depth, new_lat - displacement, new_lon + displacement]
672+
673+
left_up = math.sqrt(U_left_up**2 + V_left_up**2)
674+
left_down = math.sqrt(U_left_down**2 + V_left_down**2)
675+
right_up = math.sqrt(U_right_up**2 + V_right_up**2)
676+
right_down = math.sqrt(U_right_down**2 + V_right_down**2)
677+
678+
max_vel = left_up
679+
U_dir = -1.
680+
V_dir = 1.
681+
if max_vel < left_down:
682+
max_vel = left_down
683+
U_dir = 1.
684+
V_dir = -1.
685+
if max_vel < right_up:
686+
max_vel = right_up
687+
U_dir = 1.
688+
V_dir = 1.
689+
if max_vel < right_down:
690+
max_vel = right_down
691+
U_dir = 1.
692+
V_dir = -1.
693+
694+
particle_dlon += U_dir * unbeach_U * particle.dt # noqa
695+
particle_dlat += V_dir * unbeach_V * particle.dt # noqa
696+
697+
576698
def checkThroughBathymetry(particle, fieldset, time):
577699
"""Bathymetry error kernel.
578700
@@ -607,6 +729,35 @@ def checkThroughBathymetry(particle, fieldset, time):
607729
particle_ddepth = 3900. - particle.depth # noqa
608730

609731

732+
def reflectAtBathymetry(particle, fieldset, time):
733+
"""Reflect at bathymetry kernel.
734+
735+
Description
736+
----------
737+
A simple kernel to reflect particles at the ocean bathymetry, if they go through the bathymetry.
738+
739+
Parameter Requirements
740+
----------
741+
Fieldset:
742+
- `fieldset.bathymetry` - A 2D field containing the ocean bathymetry. Units [m].
743+
744+
Kernel Requirements
745+
----------
746+
Order of Operations:
747+
This kernel should be performed after all other movement kernels, as it samples the
748+
bathymetry field using the updated particle position.
749+
"""
750+
local_bathymetry = fieldset.bathymetry[time, particle.depth, particle.lat, particle.lon]
751+
potential_depth = particle.depth + particle_ddepth # noqa
752+
753+
if potential_depth > 100:
754+
local_bathymetry = 0.99*local_bathymetry # Handle linear interpolation issues for deep particles
755+
756+
if potential_depth > local_bathymetry:
757+
beyond_depth = potential_depth - local_bathymetry
758+
particle.depth = local_bathymetry - beyond_depth # Reflect the particle back above the bathymetry
759+
particle_ddepth = 0. # noqa
760+
610761

611762
def periodicBC(particle, fieldset, time):
612763
"""A periodic boundary condition kernel.

0 commit comments

Comments
 (0)