11"""Test the simulation of drifters."""
22
33import datetime
4+ from typing import ClassVar
45
56import numpy as np
67import xarray as xr
7-
88from parcels import FieldSet
9+
910from virtualship .instruments .drifter import Drifter , DrifterInstrument
1011from virtualship .models import Location , Spacetime
1112from 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