Skip to content

Commit 2db5710

Browse files
authored
Merge pull request #1621 from AchrefAO/Grounding_Degrounding_Tides
Grounding degrounding tides
2 parents ce6ec69 + 4789f86 commit 2db5710

File tree

2 files changed

+80
-30
lines changed

2 files changed

+80
-30
lines changed

opendrift/models/openberg.py

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#
1515
# Copyright 2015, 2023, Knut-Frode Dagestad, MET Norway
1616
# Copyright 2024, Lenny Hucher, NERSC, Norway
17-
# Copyright 2023, 2024, Achref Othmani, NERSC, Norway
17+
# Copyright 2023, 2024, 2025 Achref Othmani, NERSC, Norway
1818

1919
"""
2020
This code is initiated from the following reference with posterior modifications.
@@ -34,20 +34,19 @@
3434

3535

3636
# Constants
37-
rho_water = 1027 # Density of water (kg/m^3)
38-
rho_air = 1.293 # Density of air (kg/m^3)
39-
rho_ice = 917 # Density of ice (kg/m^3)
40-
rho_iceb = 900 # Density of iceberg (kg/m^3)
41-
g = 9.81 # Acceleration due to gravity in m/s²
42-
omega = 7.2921e-5 # Angular frequency (rad/s)
43-
csi = 1 # Sea ice coefficient of resistance
44-
wave_drag_coef = 0.3 # Wave drag coefficient
37+
rho_water = 1027 # Density of water (kg/m^3)
38+
rho_air = 1.293 # Density of air (kg/m^3)
39+
rho_ice = 917 # Density of ice (kg/m^3)
40+
rho_iceb = 900 # Density of iceberg (kg/m^3)
41+
g = 9.81 # Acceleration due to gravity in m/s²
42+
omega = 7.2921e-5 # Angular frequency (rad/s)
43+
csi = 1 # Sea ice coefficient of resistance
44+
wave_drag_coef = 0.3 # Wave drag coefficient
4545

4646

4747

4848
class IcebergObj(LagrangianArray):
49-
""" Extending Lagrangian3DArray with relevant properties for an Iceberg """
50-
49+
""" Extending LagrangianArray with relevant properties for an Iceberg """
5150
variables = LagrangianArray.add_variables([
5251
('sail', {'dtype': np.float32,
5352
'units': 'm',
@@ -69,7 +68,7 @@ class IcebergObj(LagrangianArray):
6968
'default': 30,
7069
'description': 'Width of iceberg)',
7170
'level': CONFIG_LEVEL_ESSENTIAL}),
72-
('weight_coeff', {'dtype': np.float32, # Relative to the shape of iceberg (e.g. 1 for tabular; 0.3 for pinnacle: It affects the mass only !)
71+
('weight_coeff', {'dtype': np.float32, # Relative to the shape of iceberg (e.g. 1 for tabular; 0.3 for pinnacle: It affects the mass only)
7372
'units': '1',
7473
'default': 1}),
7574
('water_drag_coeff', {'dtype': np.float32, # Ocean drag coeff.
@@ -133,8 +132,8 @@ def wave_radiation_force(rho_water, wave_height, wave_direction, iceb_length):
133132
wave_direction : Wave direction
134133
iceb_length : Iceberg's length
135134
"""
136-
F_wave_x = (0.5 * rho_water * wave_drag_coef * g * iceb_length * (wave_height / 2) ** 2 * np.sin(np.deg2rad((wave_direction + 180) % 360)))
137-
F_wave_y = (0.5 * rho_water * wave_drag_coef * g * iceb_length * (wave_height / 2) ** 2 * np.cos(np.deg2rad((wave_direction + 180) % 360)))
135+
F_wave_x = (0.5 * rho_water * wave_drag_coef * g * iceb_length * (wave_height / 2) ** 2 * np.sin(np.deg2rad(wave_direction)))
136+
F_wave_y = (0.5 * rho_water * wave_drag_coef * g * iceb_length * (wave_height / 2) ** 2 * np.cos(np.deg2rad(wave_direction)))
138137
return np.array([F_wave_x, F_wave_y])
139138

140139

@@ -276,6 +275,7 @@ class OpenBerg(OpenDriftSimulation):
276275
"x_sea_water_velocity": {"fallback": None, "profiles": True},
277276
"y_sea_water_velocity": {"fallback": None, "profiles": True},
278277
"sea_floor_depth_below_sea_level": {"fallback": 10000},
278+
'sea_surface_height': {'fallback': 0},
279279
"sea_surface_x_slope": {"fallback": 0},
280280
"sea_surface_y_slope": {"fallback": 0},
281281
"x_wind": {"fallback": None, "important": True},
@@ -300,12 +300,21 @@ def get_profile_masked(self, variable):
300300
"""
301301
draft = self.elements.draft
302302
profile = self.environment_profiles[variable]
303-
z = self.environment_profiles["z"]
303+
z = self.environment_profiles["z"]
304+
if profile.ndim == 1:
305+
profile = profile[np.newaxis, :]
304306
if z is None or (len(z) == 1 and z[0] is None):
305-
z = np.zeros_like(profile)
306-
mask = draft[:, np.newaxis] < -z
307-
mask[np.argmax(mask, axis=0), np.arange(mask.shape[1])] = False
308-
return np.ma.masked_array(profile, mask.T, fill_value=np.nan)
307+
z = np.zeros(profile.shape[0])
308+
z = np.atleast_1d(z)
309+
if z.ndim == 1:
310+
z = z[:, np.newaxis]
311+
draft = np.atleast_1d(draft)
312+
mask = draft[np.newaxis, :] < -z
313+
if mask.shape[0] > 1:
314+
mask[np.argmax(mask, axis=0), np.arange(mask.shape[1])] = False
315+
assert profile.shape == mask.shape, f"Incompatible shapes: profile {profile.shape}, mask {mask.shape}"
316+
317+
return np.ma.masked_array(profile, mask, fill_value=np.nan)
309318

310319

311320
def get_basal_env(self, variable):
@@ -327,7 +336,7 @@ def __init__(self, *args, **kwargs):
327336
},
328337
'drift:stokes_drift':{
329338
'type': 'bool',
330-
'default': True,
339+
'default': False,
331340
'description': 'If True, stokes drift force is added',
332341
'level': CONFIG_LEVEL_BASIC
333342
},
@@ -339,7 +348,7 @@ def __init__(self, *args, **kwargs):
339348
},
340349
'drift:sea_surface_slope':{
341350
'type': 'bool',
342-
'default': True,
351+
'default': False,
343352
'description': 'If True, sea surface slope force is added',
344353
'level': CONFIG_LEVEL_BASIC,
345354
},
@@ -403,6 +412,7 @@ def advect_iceberg(self):
403412
rho_water = PhysicsMethods.sea_water_density(T, S)
404413
sea_slope_x = self.environment.sea_surface_x_slope
405414
sea_slope_y = self.environment.sea_surface_y_slope
415+
sea_surface_height= self.environment.sea_surface_height
406416
wave_height = self.environment.sea_surface_wave_significant_height
407417
wave_direction = self.environment.sea_surface_wave_from_direction
408418
sea_ice_thickness = self.environment.sea_ice_thickness
@@ -438,7 +448,7 @@ def advect_iceberg(self):
438448
vprof_mean_inter = (vprof[1:] + vprof[:-1]) / 2
439449
mask = mask[:-1]
440450
thickness_reshaped = np.tile(thickness, (1, mask.shape[1]))
441-
thickness_reshaped[mask] = np.nan
451+
thickness_reshaped = np.where(mask, np.nan, thickness_reshaped)
442452
umean = np.nansum(thickness_reshaped * uprof_mean_inter, axis=0) / np.nansum(thickness_reshaped, axis=0)
443453
vmean = np.nansum(thickness_reshaped * vprof_mean_inter, axis=0) / np.nansum(thickness_reshaped, axis=0)
444454
water_vel = np.array([umean, vmean])
@@ -471,11 +481,29 @@ def dynamic(t,iceb_vel, water_vel, wind_vel, wave_height, wave_direction, Ao,
471481
V0 = advect_iceberg_no_acc(f, water_vel, wind_vel) # Approximation of the solution of the dynamic equation for the iceberg velocity
472482
V0[:, sea_ice_conc >= 0.9] = sea_ice_vel[:, sea_ice_conc >= 0.9] # With this criterium, the iceberg moves with the sea ice
473483
V0 = V0.flatten() # V0 needs to be 1D
474-
hwall = draft - water_depth
484+
485+
effective_water_depth = water_depth + sea_surface_height
486+
hwall = draft - effective_water_depth
475487
grounded = np.logical_and(hwall >= 0, grounding)
476-
if any(grounded) and grounding:
477-
logger.debug(f"Grounding condition : Icebergs grounded = {len(hwall[hwall>0])}, hwall={np.round(hwall[hwall>0],3)} meters")
478-
self.elements.moving[grounded] = 0 # Grounded icebergs shall not move, also not with diffusivity
488+
if grounding:
489+
# Determine which icebergs are grounded
490+
if np.any(grounded):
491+
logger.debug(f"Grounding condition: Icebergs grounded = {np.sum(grounded)}, "
492+
f"hwall = {np.round(hwall[grounded], 3)} meters")
493+
# Grounded icebergs stop moving
494+
self.elements.moving[grounded] = 0
495+
else:
496+
logger.debug("No grounded icebergs detected in this timestep")
497+
498+
# Check for Degrounding regardless of whether grounding occurred now
499+
degrounding = np.logical_and(self.elements.moving == 0, hwall < 0)
500+
if np.any(degrounding):
501+
logger.debug(f"Degrounding condition: Icebergs degrounded = {np.sum(degrounding)}, "
502+
f"hwall = {np.round(hwall[degrounding], 3)} meters")
503+
# Degrounded icebergs start moving again
504+
self.elements.moving[degrounding] = 1
505+
else:
506+
logger.debug("Grounding process disabled in configuration")
479507

480508
sol = solve_ivp(dynamic, [0, self.time_step.total_seconds()], V0,
481509
args=(water_vel, wind_vel, wave_height, wave_direction, Ao, Aa, rho_water,

tests/models/test_openberg.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
# along with OpenDrift. If not, see <https://www.gnu.org/licenses/>.
1717
#
1818
# Copyright 2015, Knut-Frode Dagestad, MET Norway
19+
# Copyright 2025 Achref Othmani, NERSC, Norway
1920

2021
from datetime import datetime, timedelta
2122
import numpy as np
2223
from opendrift import test_data_folder as tdf
2324
from opendrift.models.openberg import OpenBerg
2425
from opendrift.readers import reader_netCDF_CF_generic
26+
from opendrift.readers import reader_oscillating
2527

2628
"""Tests for OpenBerg module."""
2729

@@ -61,10 +63,8 @@ def test_openberg_constant_forcing():
6163
o.set_config('drift:coriolis', True)
6264
o.seed_elements(4, 60, time=datetime.now())
6365
o.run(steps=2)
64-
# The below is not expected: when adding eastwards wind, we expect iceberg to move
65-
# further eastwards, but we see that this is not the case
66-
np.testing.assert_almost_equal(o.result.lon[0][1], 4.0059, 3)
67-
np.testing.assert_almost_equal(o.result.lat[0][1], 60.0056, 3)
66+
np.testing.assert_almost_equal(o.result.lon[0][1], 4.018, 3)
67+
np.testing.assert_almost_equal(o.result.lat[0][1], 60.057, 3)
6868

6969

7070
def test_openberg_norkyst():
@@ -79,3 +79,25 @@ def test_openberg_norkyst():
7979

8080
np.testing.assert_almost_equal(o.result.lon[0][1], 3.998, 3)
8181
np.testing.assert_almost_equal(o.result.lat[0][1], 62.013, 3)
82+
83+
84+
def test_grounding_and_degrounding():
85+
o = OpenBerg(loglevel=0)
86+
reader_osc = reader_oscillating.Reader('sea_surface_height',
87+
amplitude=2,
88+
period_seconds=3600 * 6,
89+
zero_time=datetime(2020,1,1))
90+
o.add_reader(reader_osc)
91+
o.set_config('environment:constant:x_sea_water_velocity', 0)
92+
o.set_config('environment:constant:y_sea_water_velocity', 0)
93+
o.set_config('environment:constant:x_wind', 0)
94+
o.set_config('environment:constant:y_wind', 0)
95+
o.set_config('environment:constant:sea_floor_depth_below_sea_level', 20)
96+
o.set_config('processes:roll_over', False)
97+
98+
drafts=np.arange(15,23)
99+
o.seed_elements(lon=18.127, lat=74.776, time=reader_osc.zero_time,
100+
number=len(drafts),
101+
sail=10,draft=drafts,length=40,width=20)
102+
o.run(duration=timedelta(hours=12), time_step=3600)
103+
np.testing.assert_array_equal(o.result.moving.sum(dim='time'), [13., 13., 13., 12., 8., 6., 4., 1.])

0 commit comments

Comments
 (0)