Skip to content

Commit 54713a5

Browse files
committed
work towards propagating instrument problems
1 parent 5e5c211 commit 54713a5

2 files changed

Lines changed: 49 additions & 103 deletions

File tree

src/virtualship/cli/_run.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def _run(
5656
print("╚═════════════════════════════════════════════════╝")
5757

5858
if from_data is None:
59-
# TODO: caution, if collaborative environments, will this mean everyone uses the same credentials file?
60-
# TODO: need to think about how to deal with this for when using collaborative environments AND streaming data via copernicusmarine
59+
# TODO: caution, if collaborative environments (or the same machine), this will mean that multiple users share the same copernicusmarine credentials file
60+
# TODO: deal with this for if/when using collaborative environments (same machine) and streaming data from Copernicus Marine Service?
6161
COPERNICUS_CREDS_FILE = os.path.expandvars(
6262
"$HOME/.copernicusmarine/.copernicusmarine-credentials"
6363
)
@@ -129,28 +129,25 @@ def _run(
129129

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

132-
# identify problems
133-
# TODO: prob_level needs to be parsed from CLI args
134-
problem_simulator = ProblemSimulator(
135-
expedition.schedule, prob_level, expedition_dir
136-
)
137-
problems = problem_simulator.select_problems()
138-
139132
# simulate measurements
140133
print("\nSimulating measurements. This may take a while...\n")
141134

142-
# TODO: logic for getting simulations to carry on from last checkpoint! Building on .zarr files already created...
143-
144135
instruments_in_expedition = expedition.get_instruments()
145136

137+
# problems
138+
# TODO: prob_level needs to be parsed from CLI args
139+
problem_simulator = ProblemSimulator(
140+
expedition.schedule, prob_level, expedition_dir
141+
)
142+
problems = problem_simulator.select_problems(instruments_in_expedition)
143+
146144
for itype in instruments_in_expedition:
147-
#! 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
148-
# 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
149-
# propagate problems
145+
#! TODO: need logic for skipping simulation of instruments which have already been simulated successfully in a previous run of the expedition
146+
#! TODO: and new logic for not overwriting existing zarr files if they already exist from a previous successful simulation of that instrument
150147
if problems:
151148
problem_simulator.execute(
152-
problems=problems,
153-
instrument_type=itype,
149+
problems,
150+
instrumet_type=itype,
154151
)
155152

156153
# get instrument class

src/virtualship/make_realistic/problems/simulator.py

Lines changed: 36 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
from virtualship.instruments.types import InstrumentType
1414
from virtualship.make_realistic.problems.scenarios import (
15-
CaptainSafetyDrill,
1615
CTDCableJammed,
1716
)
1817
from virtualship.models.checkpoint import Checkpoint
@@ -37,8 +36,8 @@
3736
"pre_departure": "Hang on! There could be a pre-departure problem in-port...",
3837
"during_expedition": "Oh no, a problem has occurred during the expedition, at waypoint {waypoint_i}...!",
3938
"simulation_paused": "Please update your schedule (`virtualship plan` or directly in {expedition_yaml}) to account for the delay at waypoint {waypoint_i} and continue the expedition by executing the `virtualship run` command again.\nCheckpoint has been saved to {checkpoint_path}.\n",
40-
"problem_avoided": "Phew! You had enough contingency time scheduled to avoid delays from this problem. The expedition can carry on shortly...\n",
4139
"pre_departure_delay": "This problem will cause a delay of {delay_duration} hours to the whole expedition schedule. Please account for this for all waypoints in your schedule (`virtualship plan` or directly in {expedition_yaml}), then continue the expedition by executing the `virtualship run` command again.\n",
40+
"problem_avoided": "Phew! You had enough contingency time scheduled to avoid delays from this problem. The expedition can carry on shortly...\n",
4241
}
4342

4443

@@ -53,90 +52,75 @@ def __init__(self, schedule: Schedule, prob_level: int, expedition_dir: str | Pa
5352

5453
def select_problems(
5554
self,
55+
prob_level,
56+
instruments_in_expedition: set[InstrumentType],
5657
) -> dict[str, list[GeneralProblem | InstrumentProblem]] | None:
5758
"""Propagate both general and instrument problems."""
5859
# TODO: whether a problem can reoccur or not needs to be handled here too!
59-
probability = self._calc_prob()
60-
probability = 1.0 # TODO: temporary override for testing!!
61-
if probability > 0.0:
62-
problems = {}
63-
problems["general"] = self._general_problem_select(probability)
64-
problems["instrument"] = self._instrument_problem_select(probability)
65-
return problems
66-
else:
67-
return None
60+
if prob_level > 0:
61+
return self._problem_select(prob_level, instruments_in_expedition)
6862

6963
def execute(
7064
self,
7165
problems: dict[str, list[GeneralProblem | InstrumentProblem]],
72-
instrument_type: InstrumentType | None = None,
66+
instrument_type_validation: InstrumentType | None = None,
7367
log_delay: float = 7.0,
7468
):
7569
"""
7670
Execute the selected problems, returning messaging and delay times.
7771
78-
N.B. the problem_waypoint_i is different to the failed_waypoint_i defined in the Checkpoint class; the failed_waypoint_i is the waypoint index after the problem_waypoint_i where the problem occurred, as this is when scheduling issues would be encountered.
72+
N.B. a problem_waypoint_i is different to a failed_waypoint_i defined in the Checkpoint class; failed_waypoint_i is the waypoint index after the problem_waypoint_i where the problem occurred, as this is when scheduling issues would be encountered.
7973
"""
80-
# TODO: integration with which zarr files have been written so far?
81-
# TODO: logic to determine whether user has made the necessary changes to the schedule to account for the problem's delay_duration when next running the simulation... (does this come in here or _run?)
82-
# TODO: logic for whether the user has already scheduled in enough contingency time to account for the problem's delay_duration, and they get a well done message if so
83-
# TODO: need logic for if the problem can reoccur or not / and or that it has already occurred and has been addressed
84-
8574
# TODO: re: prob levels:
8675
# 0 = no problems
8776
# 1 = only one problem in expedition (either pre-departure or during expedition, general or instrument) [and set this to DEFAULT prob level]
8877
# 2 = multiple problems can occur (general and instrument), but only one pre-departure problem allowed
8978

90-
# TODO: what to do about fact that students can avoid all problems by just scheduling in enough contingency time??
91-
# this should probably be a learning point though, so maybe it's fine...
92-
#! though could then ensure that if they pass because of contingency time, they definitely get a pre-depature problem...?
93-
# this would all probably have to be a bit asynchronous, which might make things more complicated...
94-
95-
#! TODO: logic as well for case where problem can reoccur but it can only reoccur at a waypoint different to the one it has already occurred at
96-
9779
# TODO: N.B. there is not logic currently controlling how many problems can occur in total during an expedition; at the moment it can happen every time the expedition is run if it's a different waypoint / problem combination
9880

99-
general_problems = problems["general"]
100-
instrument_problems = problems["instrument"]
81+
#! TODO: what happens if students decide to re-run the expedition multiple times with slightly changed set-ups to try to e.g. get more measurements? Maybe it should be that problems are ignored if the exact expedition.yaml has been run before, and if there's any changes to the expedition.yaml
82+
# TODO: for this reason, `problems_encountered` dir should be housed in `results` dir along with a cache of the expedition.yaml used for that run...
83+
# TODO: and the results dir given a unique name which can be used to check against when re-running the expedition?
10184

10285
# allow only one pre-departure problem to occur
103-
pre_departure_problems = [p for p in general_problems if p.pre_departure]
86+
pre_departure_problems = [p for p in problems if isinstance(p, GeneralProblem)]
10487
if len(pre_departure_problems) > 1:
10588
to_keep = random.choice(pre_departure_problems)
106-
general_problems = [
107-
p for p in general_problems if not p.pre_departure or p is to_keep
108-
]
109-
# ensure any pre-departure problem is first in list
110-
general_problems.sort(key=lambda x: x.pre_departure, reverse=True)
89+
problems = [p for p in problems if not p.pre_departure or p is to_keep]
90+
91+
problems.sort(
92+
key=lambda x: x.pre_departure, reverse=True
93+
) # ensure any pre-departure problem is first
11194

11295
# TODO: make the log output stand out more visually
113-
# general problems
114-
for gproblem in general_problems:
115-
# determine problem waypoint index (random if during expedition)
96+
for p in problems:
97+
# skip if instrument problem but `p.instrument_type` does not match `instrument_type_validation`
98+
if (
99+
isinstance(p, InstrumentProblem)
100+
and p.instrument_type is not instrument_type_validation
101+
):
102+
continue
103+
116104
problem_waypoint_i = (
117105
None
118-
if gproblem.pre_departure
106+
if p.pre_departure
119107
else np.random.randint(
120108
0, len(self.schedule.waypoints) - 1
121109
) # last waypoint excluded (would not impact any future scheduling)
122110
)
123111

124-
# mark problem by unique hash and log to json, use to assess whether problem has already occurred
125-
gproblem_hash = self._make_hash(
126-
gproblem.message + str(problem_waypoint_i), 8
127-
)
112+
# TODO: double check the hashing still works as expected when problem_waypoint_i is None (i.e. pre-departure problem)
113+
problem_hash = self._make_hash(p.message + str(problem_waypoint_i), 8)
128114
hash_path = Path(
129115
self.expedition_dir
130-
/ f"{PROBLEMS_ENCOUNTERED_DIR}/problem_{gproblem_hash}.json"
116+
/ f"{PROBLEMS_ENCOUNTERED_DIR}/problem_{problem_hash}.json"
131117
)
132118
if hash_path.exists():
133119
continue # problem * waypoint combination has already occurred; don't repeat
134120
else:
135-
self._hash_to_json(
136-
gproblem, gproblem_hash, problem_waypoint_i, hash_path
137-
)
121+
self._hash_to_json(p, problem_hash, problem_waypoint_i, hash_path)
138122

139-
if gproblem.pre_departure:
123+
if isinstance(p, GeneralProblem) and p.pre_departure:
140124
alert_msg = LOG_MESSAGING["pre_departure"]
141125

142126
else:
@@ -145,48 +129,13 @@ def execute(
145129
)
146130

147131
# log problem occurrence, save to checkpoint, and pause simulation
148-
self._log_problem(gproblem, problem_waypoint_i, alert_msg, log_delay)
149-
150-
# instrument problems
151-
for i, problem in enumerate(problems["instrument"]):
152-
pass # TODO: implement!!
153-
# TODO: similar logic to above for instrument-specific problems... or combine?
154-
155-
def _propagate_general_problems(self):
156-
"""Propagate general problems based on probability."""
157-
probability = self._calc_general_prob(self.schedule, prob_level=self.prob_level)
158-
return self._general_problem_select(probability)
159-
160-
def _propagate_instrument_problems(self):
161-
"""Propagate instrument problems based on probability."""
162-
probability = self._calc_instrument_prob(
163-
self.schedule, prob_level=self.prob_level
164-
)
165-
return self._instrument_problem_select(probability)
166-
167-
def _calc_prob(self) -> float:
168-
"""
169-
Calcuates probability of a general problem as function of expedition duration and prob-level.
170-
171-
TODO: for now, general and instrument-specific problems have the same probability of occurence. Separating this out and allowing their probabilities to be set independently may be useful in future.
172-
"""
173-
if self.prob_level == 0:
174-
return 0.0
175-
176-
def _general_problem_select(self, probability) -> list[GeneralProblem]:
177-
"""Select which problems. Higher probability (tied to expedition duration) means more problems are likely to occur."""
178-
return [
179-
CaptainSafetyDrill,
180-
] # TODO: temporary placeholder!!
181-
182-
def _instrument_problem_select(self, probability) -> list[InstrumentProblem]:
183-
"""Select which problems. Higher probability (tied to expedition duration) means more problems are likely to occur."""
184-
# set: waypoint instruments vs. list of instrument-specific problems (automated registry)
185-
# will deterimne which instrument-specific problems are possible at this waypoint
186-
187-
# wp_instruments = self.schedule.waypoints.instruments
132+
self._log_problem(p, problem_waypoint_i, alert_msg, log_delay)
188133

189-
return [CTDCableJammed]
134+
def _problem_select(
135+
self, prob_level, instruments_in_schedule
136+
) -> list[GeneralProblem | InstrumentProblem]:
137+
"""Select which problems (selected from general or instrument problems). Higher probability (tied to expedition duration) means more problems are likely to occur."""
138+
return [CTDCableJammed] # TODO: temporary placeholder!!
190139

191140
def _log_problem(
192141
self,

0 commit comments

Comments
 (0)