From be1d9a4a9c75a4bfba458a87765db3ae4de440f0 Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:10:33 +0100 Subject: [PATCH 1/7] update default CTD(_BGC) stationkeeping time --- src/virtualship/static/expedition.yaml | 4 ++-- tests/expedition/expedition_dir/expedition.yaml | 4 ++-- tests/expedition/test_simulate_schedule.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/virtualship/static/expedition.yaml b/src/virtualship/static/expedition.yaml index 2b770735..776e6172 100644 --- a/src/virtualship/static/expedition.yaml +++ b/src/virtualship/static/expedition.yaml @@ -56,11 +56,11 @@ instruments_config: ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 20.0 + stationkeeping_time_minutes: 35.0 ctd_bgc_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 20.0 + stationkeeping_time_minutes: 35.0 drifter_config: depth_meter: -1.0 lifetime_minutes: 60480.0 diff --git a/tests/expedition/expedition_dir/expedition.yaml b/tests/expedition/expedition_dir/expedition.yaml index 0ed9e5f4..96b5f822 100644 --- a/tests/expedition/expedition_dir/expedition.yaml +++ b/tests/expedition/expedition_dir/expedition.yaml @@ -32,11 +32,11 @@ instruments_config: ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 20.0 + stationkeeping_time_minutes: 35.0 ctd_bgc_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 20.0 + stationkeeping_time_minutes: 35.0 drifter_config: depth_meter: -1.0 lifetime_minutes: 40320.0 diff --git a/tests/expedition/test_simulate_schedule.py b/tests/expedition/test_simulate_schedule.py index bad8c9ad..b1fbd6b4 100644 --- a/tests/expedition/test_simulate_schedule.py +++ b/tests/expedition/test_simulate_schedule.py @@ -54,8 +54,8 @@ def test_time_in_minutes_in_ship_schedule() -> None: "expedition_dir/expedition.yaml" ).instruments_config assert instruments_config.adcp_config.period == timedelta(minutes=5) - assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=20) + assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=35) assert instruments_config.ctd_bgc_config.stationkeeping_time == timedelta( - minutes=20 + minutes=35 ) assert instruments_config.ship_underwater_st_config.period == timedelta(minutes=5) From 3ae73a80ca06286d7d0aa2efb43385c935ef41b8 Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:35:36 +0100 Subject: [PATCH 2/7] update CTD(_BGC) stationkeeping time to 50 mins --- src/virtualship/expedition/simulate_schedule.py | 2 ++ src/virtualship/static/expedition.yaml | 4 ++-- tests/expedition/expedition_dir/expedition.yaml | 4 ++-- tests/expedition/test_simulate_schedule.py | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/virtualship/expedition/simulate_schedule.py b/src/virtualship/expedition/simulate_schedule.py index 0a567b1c..a25c128b 100644 --- a/src/virtualship/expedition/simulate_schedule.py +++ b/src/virtualship/expedition/simulate_schedule.py @@ -268,6 +268,7 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: drift_days=self._expedition.instruments_config.argo_float_config.drift_days, ) ) + ## TODO: time for CTD and CTD_BGC should only be added on once if both instruments are used, in reality they would be the same cast elif instrument is InstrumentType.CTD: self._measurements_to_simulate.ctds.append( CTD( @@ -279,6 +280,7 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: time_costs.append( self._expedition.instruments_config.ctd_config.stationkeeping_time ) + breakpoint() elif instrument is InstrumentType.CTD_BGC: self._measurements_to_simulate.ctd_bgcs.append( CTD_BGC( diff --git a/src/virtualship/static/expedition.yaml b/src/virtualship/static/expedition.yaml index 776e6172..8adad559 100644 --- a/src/virtualship/static/expedition.yaml +++ b/src/virtualship/static/expedition.yaml @@ -56,11 +56,11 @@ instruments_config: ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 35.0 + stationkeeping_time_minutes: 50.0 ctd_bgc_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 35.0 + stationkeeping_time_minutes: 50.0 drifter_config: depth_meter: -1.0 lifetime_minutes: 60480.0 diff --git a/tests/expedition/expedition_dir/expedition.yaml b/tests/expedition/expedition_dir/expedition.yaml index 96b5f822..ea4a2da9 100644 --- a/tests/expedition/expedition_dir/expedition.yaml +++ b/tests/expedition/expedition_dir/expedition.yaml @@ -32,11 +32,11 @@ instruments_config: ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 35.0 + stationkeeping_time_minutes: 50.0 ctd_bgc_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 - stationkeeping_time_minutes: 35.0 + stationkeeping_time_minutes: 50.0 drifter_config: depth_meter: -1.0 lifetime_minutes: 40320.0 diff --git a/tests/expedition/test_simulate_schedule.py b/tests/expedition/test_simulate_schedule.py index b1fbd6b4..e3c3568d 100644 --- a/tests/expedition/test_simulate_schedule.py +++ b/tests/expedition/test_simulate_schedule.py @@ -54,8 +54,8 @@ def test_time_in_minutes_in_ship_schedule() -> None: "expedition_dir/expedition.yaml" ).instruments_config assert instruments_config.adcp_config.period == timedelta(minutes=5) - assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=35) + assert instruments_config.ctd_config.stationkeeping_time == timedelta(minutes=50) assert instruments_config.ctd_bgc_config.stationkeeping_time == timedelta( - minutes=35 + minutes=50 ) assert instruments_config.ship_underwater_st_config.period == timedelta(minutes=5) From d513cde5aff310796fa5c8f32ce700037a11d6df Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:41:07 +0100 Subject: [PATCH 3/7] add stationkeeping_time to drifter and argo configs --- src/virtualship/models/expedition.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/virtualship/models/expedition.py b/src/virtualship/models/expedition.py index 4847b10c..14885100 100644 --- a/src/virtualship/models/expedition.py +++ b/src/virtualship/models/expedition.py @@ -215,6 +215,20 @@ class ArgoFloatConfig(pydantic.BaseModel): cycle_days: float = pydantic.Field(gt=0.0) drift_days: float = pydantic.Field(gt=0.0) + stationkeeping_time: timedelta = pydantic.Field( + serialization_alias="stationkeeping_time_minutes", + validation_alias="stationkeeping_time_minutes", + gt=timedelta(), + ) + + @pydantic.field_serializer("stationkeeping_time") + def _serialize_stationkeeping_time(self, value: timedelta, _info): + return value.total_seconds() / 60.0 + + @pydantic.field_validator("stationkeeping_time", mode="before") + def _validate_stationkeeping_time(cls, value: int | float | timedelta) -> timedelta: + return _validate_numeric_mins_to_timedelta(value) + class ADCPConfig(pydantic.BaseModel): """Configuration for ADCP instrument.""" @@ -311,6 +325,11 @@ class DrifterConfig(pydantic.BaseModel): validation_alias="lifetime_minutes", gt=timedelta(), ) + stationkeeping_time: timedelta = pydantic.Field( + serialization_alias="stationkeeping_time_minutes", + validation_alias="stationkeeping_time_minutes", + gt=timedelta(), + ) model_config = pydantic.ConfigDict(populate_by_name=True) @@ -322,6 +341,14 @@ def _serialize_lifetime(self, value: timedelta, _info): def _validate_lifetime(cls, value: int | float | timedelta) -> timedelta: return _validate_numeric_mins_to_timedelta(value) + @pydantic.field_serializer("stationkeeping_time") + def _serialize_stationkeeping_time(self, value: timedelta, _info): + return value.total_seconds() / 60.0 + + @pydantic.field_validator("stationkeeping_time", mode="before") + def _validate_stationkeeping_time(cls, value: int | float | timedelta) -> timedelta: + return _validate_numeric_mins_to_timedelta(value) + class XBTConfig(pydantic.BaseModel): """Configuration for xbt instrument.""" From c7e9ee77ca764f89280d43b638667e6e75e18342 Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:46:11 +0100 Subject: [PATCH 4/7] add new model fields in _plan and relevant static files/tests --- src/virtualship/cli/_plan.py | 2 ++ src/virtualship/static/expedition.yaml | 2 ++ tests/expedition/expedition_dir/expedition.yaml | 2 ++ tests/expedition/test_simulate_schedule.py | 6 ++++++ 4 files changed, 12 insertions(+) diff --git a/src/virtualship/cli/_plan.py b/src/virtualship/cli/_plan.py index 8b2adb15..08845e0d 100644 --- a/src/virtualship/cli/_plan.py +++ b/src/virtualship/cli/_plan.py @@ -141,6 +141,7 @@ def log_exception_to_file( {"name": "vertical_speed_meter_per_second"}, {"name": "cycle_days"}, {"name": "drift_days"}, + {"name": "stationkeeping_time", "minutes": True}, ], }, "drifter_config": { @@ -149,6 +150,7 @@ def log_exception_to_file( "attributes": [ {"name": "depth_meter"}, {"name": "lifetime", "minutes": True}, + {"name": "stationkeeping_time", "minutes": True}, ], }, } diff --git a/src/virtualship/static/expedition.yaml b/src/virtualship/static/expedition.yaml index 8adad559..1c154b01 100644 --- a/src/virtualship/static/expedition.yaml +++ b/src/virtualship/static/expedition.yaml @@ -53,6 +53,7 @@ instruments_config: max_depth_meter: -2000.0 min_depth_meter: 0.0 vertical_speed_meter_per_second: -0.1 + stationkeeping_time_minutes: 20.0 ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 @@ -64,6 +65,7 @@ instruments_config: drifter_config: depth_meter: -1.0 lifetime_minutes: 60480.0 + stationkeeping_time_minutes: 20.0 xbt_config: max_depth_meter: -285.0 min_depth_meter: -2.0 diff --git a/tests/expedition/expedition_dir/expedition.yaml b/tests/expedition/expedition_dir/expedition.yaml index ea4a2da9..cd3f532a 100644 --- a/tests/expedition/expedition_dir/expedition.yaml +++ b/tests/expedition/expedition_dir/expedition.yaml @@ -29,6 +29,7 @@ instruments_config: max_depth_meter: -2000.0 min_depth_meter: 0.0 vertical_speed_meter_per_second: -0.1 + stationkeeping_time_minutes: 20.0 ctd_config: max_depth_meter: -2000.0 min_depth_meter: -11.0 @@ -40,6 +41,7 @@ instruments_config: drifter_config: depth_meter: -1.0 lifetime_minutes: 40320.0 + stationkeeping_time_minutes: 20.0 ship_underwater_st_config: period_minutes: 5.0 ship_config: diff --git a/tests/expedition/test_simulate_schedule.py b/tests/expedition/test_simulate_schedule.py index e3c3568d..ff5a3597 100644 --- a/tests/expedition/test_simulate_schedule.py +++ b/tests/expedition/test_simulate_schedule.py @@ -58,4 +58,10 @@ def test_time_in_minutes_in_ship_schedule() -> None: assert instruments_config.ctd_bgc_config.stationkeeping_time == timedelta( minutes=50 ) + assert instruments_config.argo_float_config.stationkeeping_time == timedelta( + minutes=20 + ) + assert instruments_config.drifter_config.stationkeeping_time == timedelta( + minutes=20 + ) assert instruments_config.ship_underwater_st_config.period == timedelta(minutes=5) From 06e9eb5695ff4b414b0f01a245e7513b4c065dbe Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:17:27 +0100 Subject: [PATCH 5/7] add model_config to ArgoConfig for consistency with other instrument models and to serialise properly in _plan --- src/virtualship/models/expedition.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/virtualship/models/expedition.py b/src/virtualship/models/expedition.py index 14885100..e6e80102 100644 --- a/src/virtualship/models/expedition.py +++ b/src/virtualship/models/expedition.py @@ -229,6 +229,8 @@ def _serialize_stationkeeping_time(self, value: timedelta, _info): def _validate_stationkeeping_time(cls, value: int | float | timedelta) -> timedelta: return _validate_numeric_mins_to_timedelta(value) + model_config = pydantic.ConfigDict(populate_by_name=True) + class ADCPConfig(pydantic.BaseModel): """Configuration for ADCP instrument.""" From bf1070cd0852b993d6915c76ef1a9f54b9222221 Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:17:38 +0100 Subject: [PATCH 6/7] clean up --- src/virtualship/expedition/simulate_schedule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/virtualship/expedition/simulate_schedule.py b/src/virtualship/expedition/simulate_schedule.py index a25c128b..70773dd5 100644 --- a/src/virtualship/expedition/simulate_schedule.py +++ b/src/virtualship/expedition/simulate_schedule.py @@ -280,7 +280,6 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: time_costs.append( self._expedition.instruments_config.ctd_config.stationkeeping_time ) - breakpoint() elif instrument is InstrumentType.CTD_BGC: self._measurements_to_simulate.ctd_bgcs.append( CTD_BGC( From 21cba4ccf2408d64837a063f2c07bdc35b25d179 Mon Sep 17 00:00:00 2001 From: j-atkins <106238905+j-atkins@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:25:11 +0100 Subject: [PATCH 7/7] only add time once if both CTD and CTD_BGC at same waypoint --- src/virtualship/expedition/simulate_schedule.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/virtualship/expedition/simulate_schedule.py b/src/virtualship/expedition/simulate_schedule.py index 70773dd5..e450fcc7 100644 --- a/src/virtualship/expedition/simulate_schedule.py +++ b/src/virtualship/expedition/simulate_schedule.py @@ -255,6 +255,12 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: # time costs of each measurement time_costs = [timedelta()] + # check if both CTD and CTD_BGC are present + # TODO: this can be avoided if CTD and CTD_BGC are merged into a single instrument + both_ctd_and_bgc = ( + InstrumentType.CTD in instruments and InstrumentType.CTD_BGC in instruments + ) + for instrument in instruments: if instrument is InstrumentType.ARGO_FLOAT: self._measurements_to_simulate.argo_floats.append( @@ -268,7 +274,7 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: drift_days=self._expedition.instruments_config.argo_float_config.drift_days, ) ) - ## TODO: time for CTD and CTD_BGC should only be added on once if both instruments are used, in reality they would be the same cast + elif instrument is InstrumentType.CTD: self._measurements_to_simulate.ctds.append( CTD( @@ -288,9 +294,12 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta: max_depth=self._expedition.instruments_config.ctd_bgc_config.max_depth_meter, ) ) - time_costs.append( - self._expedition.instruments_config.ctd_bgc_config.stationkeeping_time - ) + 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 + pass + else: + time_costs.append( + self._expedition.instruments_config.ctd_bgc_config.stationkeeping_time + ) elif instrument is InstrumentType.DRIFTER: self._measurements_to_simulate.drifters.append( Drifter(