Skip to content

Commit 3c08981

Browse files
committed
propagating multiple problems per expedition, bugs still to fix
1 parent 2dc6298 commit 3c08981

File tree

2 files changed

+44
-30
lines changed

2 files changed

+44
-30
lines changed

src/virtualship/make_realistic/problems/scenarios.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def is_valid() -> bool:
6464

6565
@dataclass
6666
# @register_general_problem
67-
class FoodDeliveryDelayed:
67+
class FoodDeliveryDelayed(GeneralProblem):
6868
"""Problem: Scheduled food delivery is delayed, causing a postponement of departure."""
6969

7070
message = (

src/virtualship/make_realistic/problems/simulator.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
from virtualship.instruments.types import InstrumentType
1616
from virtualship.make_realistic.problems.scenarios import (
17+
CaptainSafetyDrill,
1718
CTDCableJammed,
19+
FoodDeliveryDelayed,
1820
GeneralProblem,
1921
InstrumentProblem,
2022
)
@@ -56,11 +58,15 @@ def select_problems(
5658
self,
5759
prob_level,
5860
instruments_in_expedition: set[InstrumentType],
59-
) -> dict[str, list[GeneralProblem | InstrumentProblem]] | None:
61+
) -> list[GeneralProblem | InstrumentProblem] | None:
6062
"""Propagate both general and instrument problems."""
6163
# TODO: whether a problem can reoccur or not needs to be handled here too!
6264
if prob_level > 0:
63-
return self._problem_select(prob_level, instruments_in_expedition)
65+
return [
66+
CTDCableJammed,
67+
FoodDeliveryDelayed,
68+
CaptainSafetyDrill,
69+
] # TODO: temporary placeholder!!
6470

6571
def execute(
6672
self,
@@ -76,7 +82,7 @@ def execute(
7682
# TODO: re: prob levels:
7783
# 0 = no problems
7884
# 1 = only one problem in expedition (either pre-departure or during expedition, general or instrument) [and set this to DEFAULT prob level]
79-
# 2 = multiple problems can occur (general and instrument), but only one pre-departure problem allowed
85+
# 2 = multiple problems can occur (general and instrument; total determined by the length of the expedition), but only one pre-departure problem allowed
8086

8187
# 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
8288

@@ -85,37 +91,51 @@ def execute(
8591
# TODO: and the results dir given a unique name which can be used to check against when re-running the expedition?
8692

8793
# allow only one pre-departure problem to occur (only GeneralProblems can be pre-departure problems)
88-
pre_departure_problems = [p for p in problems if isinstance(p, GeneralProblem)]
89-
if len(pre_departure_problems) > 1:
90-
to_keep = random.choice(pre_departure_problems)
94+
95+
pre_departure_problems = [
96+
p for p in problems if issubclass(p, GeneralProblem) and p.pre_departure
97+
]
98+
if len(pre_departure_problems) > 1: # keep only one pre-departure problem
99+
to_keep = random.choice(pre_departure_problems) # pick one at random
91100
problems = [
92101
p
93102
for p in problems
94103
if not getattr(p, "pre_departure", False) or p is to_keep
95104
]
96-
problems.sort(
97-
key=lambda p: getattr(p, "pre_departure", False), reverse=True
98-
) # ensure any problem with pre_departure=True is first; default to pre_departure=False if attribute not present (as is the case for InstrumentProblem's)
99105

100-
# TODO: make the log output stand out more visually
106+
# map each problem to a [random] waypoint (or None if pre-departure)
107+
waypoint_idxs = []
101108
for p in problems:
102-
# skip if instrument problem but `p.instrument_type` does not match `instrument_type_validation`
109+
if getattr(p, "pre_departure", False):
110+
waypoint_idxs.append(None)
111+
else:
112+
waypoint_idxs.append(
113+
np.random.randint(0, len(self.expedition.schedule.waypoints) - 1)
114+
) # last waypoint excluded (would not impact any future scheduling)
115+
116+
# air problems with their waypoint indices and sort by waypoint index (pre-departure first)
117+
paired = sorted(
118+
zip(problems, waypoint_idxs, strict=True),
119+
key=lambda x: (x[1] is not None, x[1] if x[1] is not None else -1),
120+
)
121+
problems_sorted = {
122+
"problem_class": [p for p, _ in paired],
123+
"waypoint_i": [w for _, w in paired],
124+
}
125+
126+
# TODO: make the log output stand out more visually
127+
for problem, problem_waypoint_i in zip(
128+
problems_sorted["problem_class"], problems_sorted["waypoint_i"], strict=True
129+
):
130+
# skip if instrument problem but `p.instrument_type` does not match `instrument_type_validation` (i.e. the current instrument being simulated in the expedition, e.g. from _run.py)
103131
if (
104-
isinstance(p, InstrumentProblem)
105-
and p.instrument_type is not instrument_type_validation
132+
issubclass(problem, InstrumentProblem)
133+
and problem.instrument_type is not instrument_type_validation
106134
):
107135
continue
108136

109-
problem_waypoint_i = (
110-
None
111-
if getattr(p, "pre_departure", False)
112-
else np.random.randint(
113-
0, len(self.expedition.schedule.waypoints) - 1
114-
) # last waypoint excluded (would not impact any future scheduling)
115-
)
116-
117137
# TODO: double check the hashing still works as expected when problem_waypoint_i is None (i.e. pre-departure problem)
118-
problem_hash = self._make_hash(p.message + str(problem_waypoint_i), 8)
138+
problem_hash = self._make_hash(problem.message + str(problem_waypoint_i), 8)
119139
hash_path = Path(
120140
self.expedition_dir
121141
/ f"{PROBLEMS_ENCOUNTERED_DIR}/problem_{problem_hash}.json"
@@ -125,7 +145,7 @@ def execute(
125145
else:
126146
self._hash_to_json(p, problem_hash, problem_waypoint_i, hash_path)
127147

128-
if isinstance(p, GeneralProblem) and p.pre_departure:
148+
if issubclass(p, GeneralProblem) and p.pre_departure:
129149
alert_msg = LOG_MESSAGING["pre_departure"]
130150

131151
else:
@@ -136,12 +156,6 @@ def execute(
136156
# log problem occurrence, save to checkpoint, and pause simulation
137157
self._log_problem(p, problem_waypoint_i, alert_msg, log_delay)
138158

139-
def _problem_select(
140-
self, prob_level, instruments_in_schedule
141-
) -> list[GeneralProblem | InstrumentProblem]:
142-
"""Select which problems (selected from general or instrument problems). Higher probability (tied to expedition duration) means more problems are likely to occur."""
143-
return [CTDCableJammed] # TODO: temporary placeholder!!
144-
145159
def _log_problem(
146160
self,
147161
problem: GeneralProblem | InstrumentProblem,

0 commit comments

Comments
 (0)