Skip to content

Commit a9af0da

Browse files
Variable drifter depth (#306)
* tie drifter fieldset depth to confif * attr name * depths should be abs * update tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 39958d8 commit a9af0da

2 files changed

Lines changed: 121 additions & 29 deletions

File tree

src/virtualship/instruments/drifter.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ def __init__(self, expedition, from_data):
7373
}
7474
limit_spec = {
7575
"spatial": False, # no spatial limits; generate global fieldset
76-
"depth_min": 1.0, # [meters]
77-
"depth_max": 1.0, # [meters]
76+
"depth_min": abs(
77+
expedition.instruments_config.drifter_config.depth_meter
78+
), # [meters]
79+
"depth_max": abs(
80+
expedition.instruments_config.drifter_config.depth_meter
81+
), # [meters]
7882
}
7983

8084
super().__init__(

tests/instruments/test_drifter.py

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
"""Test the simulation of drifters."""
22

33
import datetime
4+
from typing import ClassVar
45

56
import numpy as np
67
import xarray as xr
7-
88
from parcels import FieldSet
9+
910
from virtualship.instruments.drifter import Drifter, DrifterInstrument
1011
from virtualship.models import Location, Spacetime
1112
from virtualship.models.expedition import Waypoint
1213

14+
BASE_TIME = datetime.datetime.strptime("1950-01-01", "%Y-%m-%d")
15+
LIFETIME = datetime.timedelta(days=1)
1316

14-
def test_simulate_drifters(tmpdir) -> None:
17+
DEPLOY_DEPTH = -1.0 # default
18+
19+
20+
def create_dummy_expedition():
1521
# arbitrary time offset for the dummy fieldset
16-
base_time = datetime.datetime.strptime("1950-01-01", "%Y-%m-%d")
1722

18-
CONST_TEMPERATURE = 1.0 # constant temperature in fieldset
23+
class DummyExpedition:
24+
class schedule:
25+
waypoints: ClassVar = [
26+
Waypoint(
27+
location=Location(
28+
1, 2
29+
), # any location is fine for dummy, actual drifter deployment locations are defined in the test functions
30+
time=BASE_TIME,
31+
),
32+
]
33+
34+
class instruments_config:
35+
class drifter_config:
36+
lifetime = LIFETIME
37+
depth_meter = DEPLOY_DEPTH
1938

20-
LIFETIME = datetime.timedelta(days=1)
39+
return DummyExpedition()
40+
41+
42+
def test_simulate_drifters(tmpdir) -> None:
43+
CONST_TEMPERATURE = 1.0 # constant temperature in fieldset
2144

2245
v = np.full((2, 2, 2), 1.0)
2346
u = np.full((2, 2, 2), 1.0)
@@ -29,8 +52,8 @@ def test_simulate_drifters(tmpdir) -> None:
2952
"lon": np.array([0.0, 10.0]),
3053
"lat": np.array([0.0, 10.0]),
3154
"time": [
32-
np.datetime64(base_time + datetime.timedelta(seconds=0)),
33-
np.datetime64(base_time + datetime.timedelta(days=3)),
55+
np.datetime64(BASE_TIME + datetime.timedelta(seconds=0)),
56+
np.datetime64(BASE_TIME + datetime.timedelta(days=3)),
3457
],
3558
},
3659
)
@@ -40,37 +63,22 @@ def test_simulate_drifters(tmpdir) -> None:
4063
Drifter(
4164
spacetime=Spacetime(
4265
location=Location(latitude=0.5, longitude=0.5),
43-
time=base_time + datetime.timedelta(days=0),
66+
time=BASE_TIME + datetime.timedelta(days=0),
4467
),
45-
depth=0.0,
68+
depth=DEPLOY_DEPTH,
4669
lifetime=datetime.timedelta(hours=2),
4770
),
4871
Drifter(
4972
spacetime=Spacetime(
5073
location=Location(latitude=1, longitude=1),
51-
time=base_time + datetime.timedelta(hours=20),
74+
time=BASE_TIME + datetime.timedelta(hours=20),
5275
),
53-
depth=0.0,
76+
depth=DEPLOY_DEPTH,
5477
lifetime=None,
5578
),
5679
]
5780

58-
# dummy expedition for DrifterInstrument
59-
class DummyExpedition:
60-
class schedule:
61-
# ruff: noqa
62-
waypoints = [
63-
Waypoint(
64-
location=Location(1, 2),
65-
time=base_time,
66-
),
67-
]
68-
69-
class instruments_config:
70-
class drifter_config:
71-
lifetime = LIFETIME
72-
73-
expedition = DummyExpedition()
81+
expedition = create_dummy_expedition()
7482
from_data = None
7583

7684
drifter_instrument = DrifterInstrument(expedition, from_data)
@@ -99,3 +107,83 @@ class drifter_config:
99107
assert np.all(temp[np.isfinite(temp)] == CONST_TEMPERATURE), (
100108
f"measured temperature does not match {drifter_i=}"
101109
)
110+
111+
112+
def test_drifter_depths(tmpdir) -> None:
113+
CONST_TEMPERATURE = 1.0 # constant temperature in fieldset
114+
115+
v = np.full((2, 2, 2, 2), 1.0)
116+
u = np.full((2, 2, 2, 2), 1.0)
117+
t = np.full((2, 2, 2, 2), CONST_TEMPERATURE)
118+
119+
# different values at depth (random)
120+
v[:, -1, :, :] = 1.0 * np.random.randint(0, 10)
121+
u[:, -1, :, :] = 1.0 * np.random.randint(0, 10)
122+
t[:, -1, :, :] = CONST_TEMPERATURE * np.random.randint(0, 10)
123+
124+
fieldset = FieldSet.from_data(
125+
{"V": v, "U": u, "T": t},
126+
{
127+
"time": [
128+
np.datetime64(BASE_TIME + datetime.timedelta(seconds=0)),
129+
np.datetime64(BASE_TIME + datetime.timedelta(days=3)),
130+
],
131+
"depth": np.array([-10, 0]),
132+
"lat": np.array([0.0, 10.0]),
133+
"lon": np.array([0.0, 10.0]),
134+
},
135+
)
136+
137+
# drifters to deploy (same time and location, but different depths)
138+
drifters = [
139+
Drifter(
140+
spacetime=Spacetime(
141+
location=Location(latitude=5.0, longitude=5.0),
142+
time=BASE_TIME + datetime.timedelta(days=0),
143+
),
144+
depth=DEPLOY_DEPTH,
145+
lifetime=datetime.timedelta(hours=12),
146+
),
147+
Drifter(
148+
spacetime=Spacetime(
149+
location=Location(latitude=5.0, longitude=5.0),
150+
time=BASE_TIME + datetime.timedelta(days=0),
151+
),
152+
depth=DEPLOY_DEPTH - 5.0, # different drogue depth
153+
lifetime=datetime.timedelta(hours=12),
154+
),
155+
]
156+
157+
expedition = create_dummy_expedition()
158+
from_data = None
159+
160+
drifter_instrument = DrifterInstrument(expedition, from_data)
161+
out_path = tmpdir.join("out.zarr")
162+
163+
drifter_instrument.load_input_data = lambda: fieldset
164+
drifter_instrument.simulate(drifters, out_path)
165+
166+
# test if output is as expected
167+
results = xr.open_zarr(out_path)
168+
169+
assert len(results.trajectory) == len(drifters)
170+
171+
drifter_surface = results.isel(trajectory=0)
172+
drifter_depth = results.isel(trajectory=1)
173+
174+
assert drifter_surface.z[0] > drifter_depth.z[0], (
175+
"Surface drifter should be at shallower depth than deeper drifter"
176+
)
177+
178+
surface_depths = drifter_surface.z.values
179+
depth_depths = drifter_depth.z.values
180+
assert np.all(surface_depths[~np.isnan(surface_depths)] == surface_depths[0]), (
181+
"Surface drifter depth should be constant"
182+
)
183+
assert np.all(depth_depths[~np.isnan(depth_depths)] == depth_depths[0]), (
184+
"Depth drifter depth should be constant"
185+
)
186+
187+
assert drifter_surface.temperature[0] != drifter_depth.temperature[0], (
188+
"Surface and deeper drifter should have different temperature measurements"
189+
)

0 commit comments

Comments
 (0)