Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b537b3b
psuedo code
j-atkins Nov 25, 2025
a53b61d
remove notes
j-atkins Nov 25, 2025
51ab308
fill some problems
ammedd Nov 26, 2025
a55e6f7
nicks changes
ammedd Nov 26, 2025
d81efeb
still pseudo-code
ammedd Dec 2, 2025
ddbf288
Merge branch 'main' into problems
j-atkins Jan 9, 2026
07df269
prepare for problem handling logic
j-atkins Jan 9, 2026
2c41f28
work in progress...
j-atkins Jan 9, 2026
1647893
moving logic to _run
j-atkins Jan 12, 2026
4b6f125
separate problem classes and separation logic
j-atkins Jan 12, 2026
d68738d
re-structure
j-atkins Jan 13, 2026
d27bbc5
progressing changes across simuate_schedule and _run
j-atkins Jan 14, 2026
b1c28c9
Merge branch 'main' into problems
j-atkins Jan 14, 2026
49e506f
checkpoint and reverification logic
j-atkins Jan 14, 2026
5e7889f
tidy up some logging etc
j-atkins Jan 15, 2026
3c7e975
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 15, 2026
c04246c
propagating general pre-departure problem
j-atkins Jan 16, 2026
d722bbc
Merge branch 'problems' of github.com:OceanParcels/virtualship into p…
j-atkins Jan 20, 2026
3f8aecc
Merge branch 'main' into problems
j-atkins Jan 20, 2026
9adec58
propagate general problem during expedition
j-atkins Jan 20, 2026
221179a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 20, 2026
790c509
fix waypoint index selection for problem waypoint vs waypoint where s…
j-atkins Jan 21, 2026
5e5c211
Merge branch 'problems' of github.com:OceanParcels/virtualship into p…
j-atkins Jan 21, 2026
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
64 changes: 50 additions & 14 deletions src/virtualship/cli/_run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""do_expedition function."""

import glob
import logging
import os
import shutil
Expand All @@ -14,11 +15,13 @@
ScheduleProblem,
simulate_schedule,
)
from virtualship.models import Schedule
from virtualship.models.checkpoint import Checkpoint
from virtualship.make_realistic.problems.simulator import ProblemSimulator
from virtualship.models import Checkpoint, Schedule
from virtualship.utils import (
CHECKPOINT,
PROBLEMS_ENCOUNTERED_DIR,
_get_expedition,
_save_checkpoint,
expedition_cost,
get_instrument_class,
)
Expand All @@ -35,7 +38,10 @@
logging.getLogger("copernicusmarine").setLevel("ERROR")


def _run(expedition_dir: str | Path, from_data: Path | None = None) -> None:
# TODO: prob-level needs to be parsed from CLI args; currently set to 1 override for testing purposes
def _run(
expedition_dir: str | Path, from_data: Path | None = None, prob_level: int = 1
) -> None:
"""
Perform an expedition, providing terminal feedback and file output.

Expand Down Expand Up @@ -73,16 +79,16 @@ def _run(expedition_dir: str | Path, from_data: Path | None = None) -> None:

expedition = _get_expedition(expedition_dir)

# Verify instruments_config file is consistent with schedule
# verify instruments_config file is consistent with schedule
expedition.instruments_config.verify(expedition)

# load last checkpoint
checkpoint = _load_checkpoint(expedition_dir)
if checkpoint is None:
checkpoint = Checkpoint(past_schedule=Schedule(waypoints=[]))

# verify that schedule and checkpoint match
checkpoint.verify(expedition.schedule)
# verify that schedule and checkpoint match, and that problems have been resolved
checkpoint.verify(expedition.schedule, expedition_dir)

print("\n---- WAYPOINT VERIFICATION ----")

Expand All @@ -96,17 +102,16 @@ def _run(expedition_dir: str | Path, from_data: Path | None = None) -> None:
projection=projection,
expedition=expedition,
)

# handle cases where user defined schedule is incompatible (i.e. not enough time between waypoints, not problems)
if isinstance(schedule_results, ScheduleProblem):
print(
f"SIMULATION PAUSED: update your schedule (`virtualship plan`) and continue the expedition by executing the `virtualship run` command again.\nCheckpoint has been saved to {expedition_dir.joinpath(CHECKPOINT)}."
)
_save_checkpoint(
Checkpoint(
past_schedule=Schedule(
waypoints=expedition.schedule.waypoints[
: schedule_results.failed_waypoint_i
]
)
past_schedule=expedition.schedule,
failed_waypoint_i=schedule_results.failed_waypoint_i,
),
expedition_dir,
)
Expand All @@ -124,12 +129,30 @@ def _run(expedition_dir: str | Path, from_data: Path | None = None) -> None:

print("\n--- MEASUREMENT SIMULATIONS ---")

# identify problems
# TODO: prob_level needs to be parsed from CLI args
problem_simulator = ProblemSimulator(
expedition.schedule, prob_level, expedition_dir
)
problems = problem_simulator.select_problems()

# simulate measurements
print("\nSimulating measurements. This may take a while...\n")

# TODO: logic for getting simulations to carry on from last checkpoint! Building on .zarr files already created...

instruments_in_expedition = expedition.get_instruments()

for itype in instruments_in_expedition:
#! TODO: move this to before the loop; determine problem selection based on instruments_in_expedition to ensure only relevant problems are selected; and then instrument problems are propagated to within the loop
# TODO: instrument-specific problems at different waypoints are where see if can get time savings by not re-simulating everything from scratch... but if it's too complex than just leave for now
# propagate problems
if problems:
problem_simulator.execute(
problems=problems,
instrument_type=itype,
)

# get instrument class
instrument_class = get_instrument_class(itype)
if instrument_class is None:
Expand Down Expand Up @@ -158,6 +181,15 @@ def _run(expedition_dir: str | Path, from_data: Path | None = None) -> None:
print(
f"Your measurements can be found in the '{expedition_dir}/results' directory."
)

# TODO: delete checkpoint file at the end of successful expedition? [it inteferes with ability to re-run expedition]

if problems:
print("\n----- RECORD OF PROBLEMS ENCOUNTERED ------")
print(
f"\nA record of problems encountered during the expedition is saved in: {expedition_dir.joinpath(PROBLEMS_ENCOUNTERED_DIR)}"
)

print("\n------------- END -------------\n")

# end timing
Expand All @@ -174,9 +206,13 @@ def _load_checkpoint(expedition_dir: Path) -> Checkpoint | None:
return None


def _save_checkpoint(checkpoint: Checkpoint, expedition_dir: Path) -> None:
file_path = expedition_dir.joinpath(CHECKPOINT)
checkpoint.to_yaml(file_path)
def _load_hashes(expedition_dir: Path) -> set[str]:
hashes_path = expedition_dir.joinpath(PROBLEMS_ENCOUNTERED_DIR)
if not hashes_path.exists():
return set()
hash_files = glob.glob(str(hashes_path / "problem_*.txt"))
hashes = {Path(f).stem.split("_")[1] for f in hash_files}
return hashes


def _write_expedition_cost(expedition, schedule_results, expedition_dir):
Expand Down
6 changes: 6 additions & 0 deletions src/virtualship/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ class CopernicusCatalogueError(Exception):
"""Error raised when a relevant product is not found in the Copernicus Catalogue."""

pass


class ProblemEncountered(Exception):
"""Error raised when a problem is encountered during simulation."""

pass
3 changes: 2 additions & 1 deletion src/virtualship/expedition/simulate_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ def simulate(self) -> ScheduleOk | ScheduleProblem:
print(
f"Waypoint {wp_i + 1} could not be reached in time. Current time: {self._time}. Waypoint time: {waypoint.time}."
"\n\nHave you ensured that your schedule includes sufficient time for taking measurements, e.g. CTD casts (in addition to the time it takes to sail between waypoints)?\n"
"**Note**, the `virtualship plan` tool will not account for measurement times when verifying the schedule, only the time it takes to sail between waypoints.\n"
"**Hint #1**, the `virtualship plan` tool will not account for measurement times when verifying the schedule, only the time it takes to sail between waypoints.\n"
"**Hint #2**: if you previously encountered any unforeseen delays (e.g. equipment failure, pre-departure delays) during your expedition, you will need to adjust the timings of **all** waypoints after the affected waypoint, not just the next one."
)
return ScheduleProblem(self._time, wp_i)
else:
Expand Down
Loading
Loading