Skip to content

Commit f0cb46d

Browse files
authored
More realistic instrument deployment timings: CTD(_BGC)s, Argos, Drifters (#251)
* update default CTD(_BGC) stationkeeping time * update CTD(_BGC) stationkeeping time to 50 mins * add stationkeeping_time to drifter and argo configs * add new model fields in _plan and relevant static files/tests * add model_config to ArgoConfig for consistency with other instrument models and to serialise properly in _plan * clean up * only add time once if both CTD and CTD_BGC at same waypoint
1 parent d413568 commit f0cb46d

File tree

6 files changed

+59
-8
lines changed

6 files changed

+59
-8
lines changed

src/virtualship/cli/_plan.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def log_exception_to_file(
141141
{"name": "vertical_speed_meter_per_second"},
142142
{"name": "cycle_days"},
143143
{"name": "drift_days"},
144+
{"name": "stationkeeping_time", "minutes": True},
144145
],
145146
},
146147
"drifter_config": {
@@ -149,6 +150,7 @@ def log_exception_to_file(
149150
"attributes": [
150151
{"name": "depth_meter"},
151152
{"name": "lifetime", "minutes": True},
153+
{"name": "stationkeeping_time", "minutes": True},
152154
],
153155
},
154156
}

src/virtualship/expedition/simulate_schedule.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
255255
# time costs of each measurement
256256
time_costs = [timedelta()]
257257

258+
# check if both CTD and CTD_BGC are present
259+
# TODO: this can be avoided if CTD and CTD_BGC are merged into a single instrument
260+
both_ctd_and_bgc = (
261+
InstrumentType.CTD in instruments and InstrumentType.CTD_BGC in instruments
262+
)
263+
258264
for instrument in instruments:
259265
if instrument is InstrumentType.ARGO_FLOAT:
260266
self._measurements_to_simulate.argo_floats.append(
@@ -268,6 +274,7 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
268274
drift_days=self._expedition.instruments_config.argo_float_config.drift_days,
269275
)
270276
)
277+
271278
elif instrument is InstrumentType.CTD:
272279
self._measurements_to_simulate.ctds.append(
273280
CTD(
@@ -287,9 +294,12 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
287294
max_depth=self._expedition.instruments_config.ctd_bgc_config.max_depth_meter,
288295
)
289296
)
290-
time_costs.append(
291-
self._expedition.instruments_config.ctd_bgc_config.stationkeeping_time
292-
)
297+
if both_ctd_and_bgc: # only need to add time cost once if both CTD and CTD_BGC are being taken; in reality they would be done on the same instrument
298+
pass
299+
else:
300+
time_costs.append(
301+
self._expedition.instruments_config.ctd_bgc_config.stationkeeping_time
302+
)
293303
elif instrument is InstrumentType.DRIFTER:
294304
self._measurements_to_simulate.drifters.append(
295305
Drifter(

src/virtualship/models/expedition.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,22 @@ class ArgoFloatConfig(pydantic.BaseModel):
215215
cycle_days: float = pydantic.Field(gt=0.0)
216216
drift_days: float = pydantic.Field(gt=0.0)
217217

218+
stationkeeping_time: timedelta = pydantic.Field(
219+
serialization_alias="stationkeeping_time_minutes",
220+
validation_alias="stationkeeping_time_minutes",
221+
gt=timedelta(),
222+
)
223+
224+
@pydantic.field_serializer("stationkeeping_time")
225+
def _serialize_stationkeeping_time(self, value: timedelta, _info):
226+
return value.total_seconds() / 60.0
227+
228+
@pydantic.field_validator("stationkeeping_time", mode="before")
229+
def _validate_stationkeeping_time(cls, value: int | float | timedelta) -> timedelta:
230+
return _validate_numeric_mins_to_timedelta(value)
231+
232+
model_config = pydantic.ConfigDict(populate_by_name=True)
233+
218234

219235
class ADCPConfig(pydantic.BaseModel):
220236
"""Configuration for ADCP instrument."""
@@ -311,6 +327,11 @@ class DrifterConfig(pydantic.BaseModel):
311327
validation_alias="lifetime_minutes",
312328
gt=timedelta(),
313329
)
330+
stationkeeping_time: timedelta = pydantic.Field(
331+
serialization_alias="stationkeeping_time_minutes",
332+
validation_alias="stationkeeping_time_minutes",
333+
gt=timedelta(),
334+
)
314335

315336
model_config = pydantic.ConfigDict(populate_by_name=True)
316337

@@ -322,6 +343,14 @@ def _serialize_lifetime(self, value: timedelta, _info):
322343
def _validate_lifetime(cls, value: int | float | timedelta) -> timedelta:
323344
return _validate_numeric_mins_to_timedelta(value)
324345

346+
@pydantic.field_serializer("stationkeeping_time")
347+
def _serialize_stationkeeping_time(self, value: timedelta, _info):
348+
return value.total_seconds() / 60.0
349+
350+
@pydantic.field_validator("stationkeeping_time", mode="before")
351+
def _validate_stationkeeping_time(cls, value: int | float | timedelta) -> timedelta:
352+
return _validate_numeric_mins_to_timedelta(value)
353+
325354

326355
class XBTConfig(pydantic.BaseModel):
327356
"""Configuration for xbt instrument."""

src/virtualship/static/expedition.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,19 @@ instruments_config:
5353
max_depth_meter: -2000.0
5454
min_depth_meter: 0.0
5555
vertical_speed_meter_per_second: -0.1
56+
stationkeeping_time_minutes: 20.0
5657
ctd_config:
5758
max_depth_meter: -2000.0
5859
min_depth_meter: -11.0
59-
stationkeeping_time_minutes: 20.0
60+
stationkeeping_time_minutes: 50.0
6061
ctd_bgc_config:
6162
max_depth_meter: -2000.0
6263
min_depth_meter: -11.0
63-
stationkeeping_time_minutes: 20.0
64+
stationkeeping_time_minutes: 50.0
6465
drifter_config:
6566
depth_meter: -1.0
6667
lifetime_minutes: 60480.0
68+
stationkeeping_time_minutes: 20.0
6769
xbt_config:
6870
max_depth_meter: -285.0
6971
min_depth_meter: -2.0

tests/expedition/expedition_dir/expedition.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,19 @@ instruments_config:
2929
max_depth_meter: -2000.0
3030
min_depth_meter: 0.0
3131
vertical_speed_meter_per_second: -0.1
32+
stationkeeping_time_minutes: 20.0
3233
ctd_config:
3334
max_depth_meter: -2000.0
3435
min_depth_meter: -11.0
35-
stationkeeping_time_minutes: 20.0
36+
stationkeeping_time_minutes: 50.0
3637
ctd_bgc_config:
3738
max_depth_meter: -2000.0
3839
min_depth_meter: -11.0
39-
stationkeeping_time_minutes: 20.0
40+
stationkeeping_time_minutes: 50.0
4041
drifter_config:
4142
depth_meter: -1.0
4243
lifetime_minutes: 40320.0
44+
stationkeeping_time_minutes: 20.0
4345
ship_underwater_st_config:
4446
period_minutes: 5.0
4547
ship_config:

tests/expedition/test_simulate_schedule.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,14 @@ def test_time_in_minutes_in_ship_schedule() -> None:
5454
"expedition_dir/expedition.yaml"
5555
).instruments_config
5656
assert instruments_config.adcp_config.period == timedelta(minutes=5)
57-
assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=20)
57+
assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=50)
5858
assert instruments_config.ctd_bgc_config.stationkeeping_time == timedelta(
59+
minutes=50
60+
)
61+
assert instruments_config.argo_float_config.stationkeeping_time == timedelta(
62+
minutes=20
63+
)
64+
assert instruments_config.drifter_config.stationkeeping_time == timedelta(
5965
minutes=20
6066
)
6167
assert instruments_config.ship_underwater_st_config.period == timedelta(minutes=5)

0 commit comments

Comments
 (0)