Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ dev = [
"pytest == 8.2.0",
"pytest-cov == 5.0.0",
"codecov == 2.1.13",
"sortedcontainers == 2.4.0",
"sortedcontainers-stubs == 2.4.2",
]

[tool.isort]
Expand Down
81 changes: 60 additions & 21 deletions tests/test_sailship.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""Performs a complete cruise with virtual ship."""

import datetime
from datetime import timedelta

import numpy as np
from parcels import Field, FieldSet

from virtual_ship import Location
from virtual_ship import InstrumentType, Location, Waypoint
from virtual_ship.sailship import sailship
from virtual_ship.virtual_ship_configuration import (
from virtual_ship.virtual_ship_config import (
ADCPConfig,
ArgoFloatConfig,
CTDConfig,
DrifterConfig,
ShipUnderwaterSTConfig,
VirtualShipConfig,
)

Expand Down Expand Up @@ -65,7 +69,7 @@ def test_sailship() -> None:
)

ship_underwater_st_fieldset = FieldSet.from_data(
{"U": 0, "V": 0, "S": 0, "T": 0},
{"U": 0, "V": 0, "salinity": 0, "temperature": 0},
{"lon": 0, "lat": 0},
)

Expand All @@ -84,35 +88,70 @@ def test_sailship() -> None:

argo_float_config = ArgoFloatConfig(
fieldset=argo_float_fieldset,
min_depth=-argo_float_fieldset.U.depth[0],
max_depth=-2000,
drift_depth=-1000,
vertical_speed=-0.10,
cycle_days=10,
drift_days=9,
)

adcp_config = ADCPConfig(max_depth=-1000, bin_size_m=24)
adcp_config = ADCPConfig(
max_depth=-1000,
bin_size_m=24,
period=timedelta(minutes=5),
fieldset=adcp_fieldset,
)

config = VirtualShipConfig(
start_time=datetime.datetime.strptime(
"2022-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S"
ship_underwater_st_config = ShipUnderwaterSTConfig(
period=timedelta(minutes=5), fieldset=ship_underwater_st_fieldset
)

ctd_config = CTDConfig(
stationkeeping_time=timedelta(minutes=20),
fieldset=ctd_fieldset,
min_depth=ctd_fieldset.U.depth[0],
max_depth=ctd_fieldset.U.depth[-1],
)

drifter_config = DrifterConfig(
fieldset=drifter_fieldset,
depth=-drifter_fieldset.U.depth[0],
lifetime=timedelta(weeks=4),
)

waypoints = [
Waypoint(
location=Location(latitude=-23.071289, longitude=63.743631),
time=base_time,
),
Waypoint(
location=Location(latitude=-23.081289, longitude=63.743631),
instrument=InstrumentType.CTD,
),
Waypoint(
location=Location(latitude=-23.181289, longitude=63.743631),
time=base_time + datetime.timedelta(hours=1),
instrument=InstrumentType.CTD,
),
Waypoint(
location=Location(latitude=-23.281289, longitude=63.743631),
instrument=InstrumentType.DRIFTER,
),
route_coordinates=[
Location(latitude=-23.071289, longitude=63.743631),
Location(latitude=-23.081289, longitude=63.743631),
Location(latitude=-23.191289, longitude=63.743631),
],
adcp_fieldset=adcp_fieldset,
ship_underwater_st_fieldset=ship_underwater_st_fieldset,
ctd_fieldset=ctd_fieldset,
drifter_fieldset=drifter_fieldset,
argo_float_deploy_locations=[
Location(latitude=-23.081289, longitude=63.743631)
],
drifter_deploy_locations=[Location(latitude=-23.081289, longitude=63.743631)],
ctd_deploy_locations=[Location(latitude=-23.081289, longitude=63.743631)],
Waypoint(
location=Location(latitude=-23.381289, longitude=63.743631),
instrument=InstrumentType.ARGO_FLOAT,
),
]

config = VirtualShipConfig(
ship_speed=5.14,
waypoints=waypoints,
argo_float_config=argo_float_config,
adcp_config=adcp_config,
ship_underwater_st_config=ship_underwater_st_config,
ctd_config=ctd_config,
drifter_config=drifter_config,
)

sailship(config)
13 changes: 12 additions & 1 deletion virtual_ship/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
"""Code for the Virtual Ship Classroom, where Marine Scientists can combine Copernicus Marine Data with an OceanParcels ship to go on a virtual expedition."""

from . import instruments, sailship
from .instrument_type import InstrumentType
from .location import Location
from .planning_error import PlanningError
from .spacetime import Spacetime
from .waypoint import Waypoint

__all__ = ["Location", "Spacetime", "instruments", "sailship"]
__all__ = [
"InstrumentType",
"Location",
"PlanningError",
"Spacetime",
"Waypoint",
"instruments",
"sailship",
]
21 changes: 18 additions & 3 deletions virtual_ship/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from datetime import timedelta

from .virtual_ship_configuration import VirtualShipConfig
from .instrument_type import InstrumentType
from .virtual_ship_config import VirtualShipConfig


def costs(config: VirtualShipConfig, total_time: timedelta):
Expand All @@ -18,8 +19,22 @@ def costs(config: VirtualShipConfig, total_time: timedelta):
argo_deploy_cost = 15000

ship_cost = ship_cost_per_day / 24 * total_time.total_seconds() // 3600
argo_cost = len(config.argo_float_deploy_locations) * argo_deploy_cost
drifter_cost = len(config.drifter_deploy_locations) * drifter_deploy_cost
num_argos = len(
[
waypoint
for waypoint in config.waypoints
if waypoint.instrument is InstrumentType.ARGO_FLOAT
]
)
argo_cost = num_argos * argo_deploy_cost
num_drifters = len(
[
waypoint
for waypoint in config.waypoints
if waypoint.instrument is InstrumentType.DRIFTER
]
)
drifter_cost = num_drifters * drifter_deploy_cost

cost = ship_cost + argo_cost + drifter_cost
return cost
11 changes: 11 additions & 0 deletions virtual_ship/instrument_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""InstrumentType Enum."""

from enum import Enum, auto


class InstrumentType(Enum):
"""Types of instruments."""

CTD = auto()
DRIFTER = auto()
ARGO_FLOAT = auto()
2 changes: 1 addition & 1 deletion virtual_ship/instruments/adcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def simulate_adcp(
sample_points: list[Spacetime],
) -> None:
"""
Use parcels to simulate an ADCP in a fieldset.
Use Parcels to simulate an ADCP in a fieldset.

:param fieldset: The fieldset to simulate the ADCP in.
:param out_path: The path to write the results to.
Expand Down
9 changes: 8 additions & 1 deletion virtual_ship/instruments/argo_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def simulate_argo_floats(
endtime: datetime | None,
) -> None:
"""
Use parcels to simulate a set of Argo floats in a fieldset.
Use Parcels to simulate a set of Argo floats in a fieldset.

:param fieldset: The fieldset to simulate the Argo floats in.
:param out_path: The path to write the results to.
Expand All @@ -133,6 +133,13 @@ def simulate_argo_floats(
"""
DT = 10.0 # dt of Argo float simulation integrator

if len(argo_floats) == 0:
print(
"No Argo floats provided. Parcels currently crashes when providing an empty particle set, so no argo floats simulation will be done and no files will be created."
)
# TODO when Parcels supports it this check can be removed.
return

# define parcel particles
argo_float_particleset = ParticleSet(
fieldset=fieldset,
Expand Down
9 changes: 8 additions & 1 deletion virtual_ship/instruments/ctd.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def simulate_ctd(
outputdt: timedelta,
) -> None:
"""
Use parcels to simulate a set of CTDs in a fieldset.
Use Parcels to simulate a set of CTDs in a fieldset.

:param fieldset: The fieldset to simulate the CTDs in.
:param out_path: The path to write the results to.
Expand All @@ -71,6 +71,13 @@ def simulate_ctd(
WINCH_SPEED = 1.0 # sink and rise speed in m/s
DT = 10.0 # dt of CTD simulation integrator

if len(ctds) == 0:
print(
"No CTDs provided. Parcels currently crashes when providing an empty particle set, so no CTD simulation will be done and no files will be created."
)
# TODO when Parcels supports it this check can be removed.
return

fieldset_starttime = fieldset.time_origin.fulltime(fieldset.U.grid.time_full[0])
fieldset_endtime = fieldset.time_origin.fulltime(fieldset.U.grid.time_full[-1])

Expand Down
9 changes: 8 additions & 1 deletion virtual_ship/instruments/drifter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def simulate_drifters(
endtime: datetime | None,
) -> None:
"""
Use parcels to simulate a set of drifters in a fieldset.
Use Parcels to simulate a set of drifters in a fieldset.

:param fieldset: The fieldset to simulate the Drifters in.
:param out_path: The path to write the results to.
Expand All @@ -58,6 +58,13 @@ def simulate_drifters(
:param dt: Dt for integration.
:param endtime: Stop at this time, or if None, continue until the end of the fieldset or until all drifters ended. If this is earlier than the last drifter ended or later than the end of the fieldset, a warning will be printed.
"""
if len(drifters) == 0:
print(
"No drifters provided. Parcels currently crashes when providing an empty particle set, so no drifter simulation will be done and no files will be created."
)
# TODO when Parcels supports it this check can be removed.
return

# define parcel particles
drifter_particleset = ParticleSet(
fieldset=fieldset,
Expand Down
2 changes: 1 addition & 1 deletion virtual_ship/instruments/ship_underwater_st.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def simulate_ship_underwater_st(
sample_points: list[Spacetime],
) -> None:
"""
Use parcels to simulate underway data, measuring salinity and temperature at the given depth along the ship track in a fieldset.
Use Parcels to simulate underway data, measuring salinity and temperature at the given depth along the ship track in a fieldset.

:param fieldset: The fieldset to simulate the sampling in.
:param out_path: The path to write the results to.
Expand Down
7 changes: 7 additions & 0 deletions virtual_ship/planning_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""PlanningError Exception."""


class PlanningError(RuntimeError):
"""An error when checking the schedule or during sailing."""

pass
Loading