Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 22 additions & 8 deletions src/virtualship/models/expedition.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,24 @@ def verify(
raise ScheduleError(f"{wp_str} must have a specified time.")

# check waypoint times are in ascending order
timed_waypoints = [wp for wp in self.waypoints if wp.time is not None]
checks = [
next.time >= cur.time for cur, next in itertools.pairwise(timed_waypoints)
# (indices kept relative to self.waypoints, so they can be converted to public waypoint numbers)
timed_waypoints = [
(wp_i, wp) for wp_i, wp in enumerate(self.waypoints) if wp.time is not None
]
if not all(checks):
invalid_i = [i for i, c in enumerate(checks) if c]
public_wps = [_get_public_wp(i, self.waypoints) for i in invalid_i]
invalid_i = [
next_i
for (_, cur), (next_i, next) in itertools.pairwise(timed_waypoints)
if next.time < cur.time
]
if invalid_i:
invalid_labels = [
"Port of Arrival"
if _get_public_wp(i, self.waypoints) is None
else f"#{_get_public_wp(i, self.waypoints)}"
for i in invalid_i
]
raise ScheduleError(
f"Waypoint(s) {', '.join(f'#{i}' for i in public_wps)}: each waypoint should be timed after all previous waypoints",
f"Waypoint(s) {', '.join(invalid_labels)}: each waypoint should be timed after all previous waypoints",
)

# check if all non-port waypoints are in water using bathymetry data
Expand Down Expand Up @@ -234,7 +243,12 @@ def verify(
# check that ship will arrive on time at each waypoint (in case no unexpected event happen)
time = wps_in_use[0].time

for wp_i, (wp, wp_next) in enumerate(itertools.pairwise(wps_in_use)):
# offset from wps_in_use indices to self.waypoints indices (a placeholder departure port is excluded from wps_in_use)
wps_in_use_offset = 0 if self.departure_port.is_in_use else 1

for wp_i, (wp, wp_next) in enumerate(
itertools.pairwise(wps_in_use), start=wps_in_use_offset
):
stationkeeping_time = (
wp.stationkeeping_time(instruments_config)
if isinstance(wp, Waypoint)
Expand Down
31 changes: 30 additions & 1 deletion tests/expedition/test_expedition.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,19 @@ def test_verify_on_land(base_expedition):
Port(location=Location(1, 0), time=datetime(2022, 1, 3, 0, 0, 0)),
],
ScheduleError,
r"Waypoint\(s\).*?: each waypoint should be timed after all previous waypoints",
r"Waypoint\(s\) #3: each waypoint should be timed after all previous waypoints",
id="SequentialWaypoints",
),
pytest.param(
[
Port(location=Location(0, 0), time=datetime(2022, 1, 1, 0, 0, 0)),
Waypoint(location=Location(0, 0), time=datetime(2022, 1, 2, 0, 0, 0)),
Port(location=Location(1, 0), time=datetime(2022, 1, 1, 12, 0, 0)),
],
ScheduleError,
r"Waypoint\(s\) Port of Arrival: each waypoint should be timed after all previous waypoints",
id="SequentialWaypointsArrivalPort",
),
pytest.param(
[
Port(location=Location(0, 0), time=datetime(2022, 1, 1, 0, 0, 0)),
Expand All @@ -235,6 +245,25 @@ def test_verify_on_land(base_expedition):
r"Waypoint planning is not valid: would arrive too late at waypoint 2\.",
id="NotEnoughTime",
),
pytest.param(
[
Port(location=Location(None, None), time=None),
Waypoint(
location=Location(0, 0),
time=datetime(2022, 1, 1, 1, 0, 0),
instrument=[],
),
Waypoint(
location=Location(1, 0),
time=datetime(2022, 1, 1, 1, 1, 0),
instrument=[],
),
Port(location=Location(1, 0), time=datetime(2022, 1, 2, 0, 0, 0)),
],
ScheduleError,
r"Waypoint planning is not valid: would arrive too late at waypoint 2\.",
id="NotEnoughTimePlaceholderDeparturePort",
),
],
)
def test_verify_schedule_errors(base_expedition, waypoints: list, error, match) -> None:
Expand Down
Loading