Skip to content

Commit 9e04544

Browse files
committed
mobilize duration hard-coded based on vessel used, sequence yaml file ability in task, assign asset for task 1 instead of just evaluating them:
- mobizilize calcDuration is hardcoded based on the vessel used. - extract sequence yaml file from task.py so the user can edit things like durations. - update from the sequence yaml in task.py to enable user-defined durations. - changing site_distance_m to route_length_m to avoid confusion. - calwave main task1 example assigns assets to actions instead of just evaluating them.
1 parent 5759f80 commit 9e04544

File tree

5 files changed

+159
-54
lines changed

5 files changed

+159
-54
lines changed

famodel/irma/calwave_action.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -868,9 +868,29 @@ def calcDurationAndCost(self):
868868

869869
# --- Mobilization ---
870870
if self.type == 'mobilize':
871-
pass
871+
# Hard-coded example of mobilization times based on vessel type
872+
durations = {
873+
'crane_barge': 3.0,
874+
'research_vessel': 1.0
875+
}
876+
for role_name, vessel in self.assets.items():
877+
vessel_type = vessel['type'].lower()
878+
for key, duration in durations.items():
879+
if key in vessel_type:
880+
self.duration += duration
881+
break
882+
872883
elif self.type == 'demobilize':
873-
pass
884+
# Hard-coded example of demobilization times based on vessel type
885+
durations = {
886+
'crane_barge': 3.0,
887+
'research_vessel': 1.0
888+
}
889+
for role_name, vessel in self.assets.items():
890+
vessel_type = vessel['type'].lower()
891+
for key, duration in durations.items():
892+
if key in vessel_type:
893+
self.duration += duration
874894
elif self.type == 'load_cargo':
875895
pass
876896

@@ -893,7 +913,7 @@ def calcDurationAndCost(self):
893913
tr = vessel['transport']
894914

895915
# distance
896-
dist_m = float(tr['site_distance_m'])
916+
dist_m = float(tr['route_length_m'])
897917

898918
# speed: linehaul uses transport.cruise_speed_mps
899919
speed_mps = float(tr['cruise_speed_mps'])
@@ -925,7 +945,7 @@ def calcDurationAndCost(self):
925945
tr_t = tug.get('transport', {})
926946

927947
# distance: prefer barge’s transport
928-
dist_m = float(tr_b.get('site_distance_m', tr_t['site_distance_m']))
948+
dist_m = float(tr_b.get('route_length_m', tr_t['route_length_m']))
929949

930950
# speed for convoy linehaul: barge (operator) cruise speed
931951
operator = self.assets.get('operator') or self.assets.get('vessel')

famodel/irma/calwave_chart.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def view_from_task(sched_task, sc, title: str | None = None):
8484
if dur <= 0.0:
8585
continue
8686

87-
aa = getattr(a, 'assigned_assets', {}) or {}
87+
aa = getattr(a, 'assets', {}) or {}
8888

8989
# collect ALL candidate roles → multiple lanes allowed
9090
lane_keys = set()

famodel/irma/calwave_task.py

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
"""
88

99
from collections import defaultdict
10+
import yaml
1011

1112
class Task:
12-
def __init__(self, actions, action_sequence, **kwargs):
13+
def __init__(self, name, actions, action_sequence, **kwargs):
1314
'''
1415
Create a Task from a list of actions and a dependency map.
1516
1617
Parameters
1718
----------
19+
name : str
20+
Name of the task.
1821
actions : list
1922
All Action objects that are part of this task.
2023
action_sequence : dict or None
@@ -32,6 +35,7 @@ def __init__(self, actions, action_sequence, **kwargs):
3235
resource_roles = kwargs.get('resource_roles', ('vessel', 'carrier', 'operator'))
3336

3437
# ---- core storage ----
38+
self.name = name
3539
self.actions = {a.name: a for a in actions}
3640
# allow None → infer solely from Action.dependencies
3741
self.action_sequence = {k: list(v) for k, v in (action_sequence or {}).items()}
@@ -41,12 +45,13 @@ def __init__(self, actions, action_sequence, **kwargs):
4145
self.ti = 0.0
4246
self.tf = 0.0
4347
self.resource_roles = tuple(resource_roles)
44-
48+
self.enforce_resources = enforce_resources
49+
self.strategy = strategy
4550
# ---- scheduling ----
46-
if strategy == 'levels':
51+
if self.strategy == 'levels':
4752
self._schedule_by_levels()
4853
else:
49-
self._schedule_by_earliest(enforce_resources=enforce_resources)
54+
self._schedule_by_earliest(enforce_resources=self.enforce_resources)
5055

5156
# ---- roll-ups ----
5257
self.cost = sum(float(getattr(a, 'cost', 0.0) or 0.0) for a in self.actions.values())
@@ -66,7 +71,7 @@ def _names_from_dependencies(a):
6671
return clean
6772

6873
@classmethod
69-
def from_scenario(cls, sc, **kwargs):
74+
def from_scenario(cls, sc, name, **kwargs):
7075
actions = list(sc.actions.values())
7176
base = {a.name: cls._names_from_dependencies(a) for a in actions}
7277
extra = kwargs.pop('extra_dependencies', None) or {}
@@ -75,7 +80,7 @@ def from_scenario(cls, sc, **kwargs):
7580
for d in v:
7681
if d != k and d not in base[k]:
7782
base[k].append(d)
78-
return cls(actions=actions, action_sequence=base, **kwargs)
83+
return cls(name=name, actions=actions, action_sequence=base, **kwargs)
7984

8085
# --------------------------- Resource & Scheduling ---------------------------
8186

@@ -224,3 +229,95 @@ def level_of(a, path):
224229
a.period = (a.start_hr, a.end_hr)
225230
a.label_time = f'{dur:.1f}'
226231
self.tf = self.ti + self.duration
232+
233+
def extractSeqYaml(self, output_file=None):
234+
"""
235+
Extract the sequence of actions into a YAML file for user editing.
236+
237+
Args:
238+
output_file (str): The name of the output YAML file.
239+
"""
240+
# Write the sequence data to a YAML file
241+
if output_file is None:
242+
output_file = f"{self.name}_sequence.yaml"
243+
244+
# Build the YAML:
245+
task_data = []
246+
for action_name, action in self.actions.items():
247+
roles = list(action.requirements.keys())
248+
deps = list(action.dependencies.keys())
249+
asset_types = []
250+
for role, asset in action.assets.items():
251+
asset_types.append(asset['type'])
252+
253+
entry = {
254+
'action': action_name,
255+
'duration': round(float(action.duration), 2),
256+
'roles': roles,
257+
'assets': asset_types,
258+
'dependencies': deps,
259+
}
260+
task_data.append(entry)
261+
262+
yaml_dict = {self.name: task_data}
263+
264+
with open(output_file, 'w') as yaml_file:
265+
yaml.dump(yaml_dict, yaml_file, sort_keys=False)
266+
267+
print(f"Task sequence YAML file generated: {output_file}")
268+
269+
def update_from_SeqYaml(self, input_file=None):
270+
"""
271+
Update the Task object based on a user-edited YAML file.
272+
273+
Args
274+
----
275+
input_file : str, optional
276+
The name of the YAML file (default: <task_name>_sequence.yaml).
277+
"""
278+
if input_file is None:
279+
input_file = f"{self.name}_sequence.yaml"
280+
281+
# Load YAML content
282+
with open(input_file, "r") as yaml_file:
283+
seq_data = yaml.safe_load(yaml_file)
284+
285+
if self.name not in seq_data:
286+
raise ValueError(f"Task name '{self.name}' not found in YAML file.")
287+
288+
updated_actions = seq_data[self.name]
289+
290+
# Reset internal attributes
291+
self.actions_ti = {}
292+
self.duration = 0.0
293+
self.cost = 0.0
294+
self.ti = 0.0
295+
self.tf = 0.0
296+
297+
# Update each action from YAML
298+
for entry in updated_actions:
299+
a_name = entry["action"]
300+
if a_name not in self.actions:
301+
print(f"Skipping unknown action '{a_name}' (not in current task).")
302+
continue
303+
304+
a = self.actions[a_name]
305+
306+
# Update action duration
307+
a.duration = float(entry.get("duration", getattr(a, "duration", 0.0)))
308+
309+
# TODO: Update dependencies
310+
# TODO: Update roles
311+
# TODO: Update assets
312+
# TODO: Update cost
313+
314+
# ---- re-scheduling ----
315+
if self.strategy == 'levels':
316+
self._schedule_by_levels()
317+
else:
318+
self._schedule_by_earliest(enforce_resources=self.enforce_resources)
319+
320+
# ---- re-roll-ups ----
321+
self.cost = sum(float(getattr(a, 'cost', 0.0) or 0.0) for a in self.actions.values())
322+
323+
print(f"Task '{self.name}' successfully updated from YAML file: {input_file}")
Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,6 @@
1111

1212
sc = Scenario() # now sc exists in *this* session
1313

14-
def eval_set(a, roles, duration=None, **params):
15-
"""
16-
Convenience: call evaluateAssets with roles/params and optionally set .duration.
17-
Always stores assigned_assets for plotting/scheduling attribution.
18-
"""
19-
# Your Action.evaluateAssets may return (duration, cost); we still set explicit duration if passed.
20-
res = a.evaluateAssets(roles | params)
21-
if duration is not None:
22-
a.duration = float(duration)
23-
elif isinstance(res, tuple) and len(res) > 0 and res[0] is not None:
24-
try:
25-
a.duration = float(res[0])
26-
except Exception:
27-
pass
28-
# keep roles visible on the action
29-
a.assigned_assets = roles
30-
return a
31-
3214
# ---------- Core builder ----------
3315
def build_task1_calwave(sc: Scenario, project: Project):
3416
"""
@@ -144,47 +126,47 @@ def build_task1_calwave(sc: Scenario, project: Project):
144126
'linehaul_to_home': [linehome_convoy, linehome_by],
145127
'demobilize': [demob_sd, demob_by]}
146128

147-
# ---------- Evaluation step (assign vessels & durations) ----------
148-
def evaluate_task1(sc: Scenario, actions: dict):
129+
# ---------- Assignment step (assign vessels & durations) ----------
130+
def assign_actions(sc: Scenario, actions: dict):
149131
"""
150132
Assign vessels/roles and set durations where the evaluator doesn't.
151133
Keeps creation and evaluation clearly separated.
152134
"""
153135
V = sc.vessels # shorthand
154136

155137
# Mobilize
156-
eval_set(actions['mobilize'][0], {'operator': V['San_Diego']}, duration=3.0)
157-
eval_set(actions['mobilize'][1], {'operator': V['Beyster']}, duration=1.0)
158-
138+
actions['mobilize'][0].assignAssets({'operator': V['San_Diego']})
139+
actions['mobilize'][1].assignAssets({'operator': V['Beyster']})
140+
159141
# Transit to site
160142
convoy_to_site, beyster_to_site = actions['linehaul_to_site']
161-
eval_set(convoy_to_site, {'carrier': V['Jag'], 'operator': V['San_Diego']})
162-
eval_set(beyster_to_site, {'vessel': V['Beyster']})
143+
convoy_to_site.assignAssets({'carrier': V['Jag'], 'operator': V['San_Diego']})
144+
beyster_to_site.assignAssets({'vessel': V['Beyster']})
163145

164146
# Onsite convoy (tug+barge)
165147
for a_tug in actions['onsite_tug']:
166-
eval_set(a_tug, {'carrier': V['Jag'], 'operator': V['San_Diego']})
148+
a_tug.assignAssets({'carrier': V['Jag'], 'operator': V['San_Diego']})
167149

168150
# Install (Jag carries, San_Diego operates the install)
169151
for a_inst in actions['install']:
170-
eval_set(a_inst, {'carrier': V['Jag'], 'operator': V['San_Diego']})
152+
a_inst.assignAssets({'carrier': V['Jag'], 'operator': V['San_Diego']})
171153

172154
# Onsite self-propelled (Beyster)
173155
for a_by in actions['onsite_by']:
174-
eval_set(a_by, {'vessel': V['Beyster']})
156+
a_by.assignAssets({'vessel': V['Beyster']})
175157

176158
# Monitor (Beyster as support)
177159
for a_mon in actions['monitor']:
178-
eval_set(a_mon, {'support': V['Beyster']})
160+
a_mon.assignAssets({'support': V['Beyster']})
179161

180162
# Transit to home
181163
convoy_to_home, beyster_to_home = actions['linehaul_to_home']
182-
eval_set(convoy_to_home, {'carrier': V['Jag'], 'operator': V['San_Diego']})
183-
eval_set(beyster_to_home, {'vessel': V['Beyster']})
164+
convoy_to_home.assignAssets({'carrier': V['Jag'], 'operator': V['San_Diego']})
165+
beyster_to_home.assignAssets({'vessel': V['Beyster']})
184166

185167
# Demobilize
186-
eval_set(actions['demobilize'][0], {'operator': V['San_Diego']}, duration=3.0)
187-
eval_set(actions['demobilize'][1], {'operator': V['Beyster']}, duration=1.0)
168+
actions['demobilize'][0].assignAssets({'operator': V['San_Diego']})
169+
actions['demobilize'][1].assignAssets({'operator': V['Beyster']})
188170

189171

190172
if __name__ == '__main__':
@@ -198,19 +180,25 @@ def evaluate_task1(sc: Scenario, actions: dict):
198180
# 3) Build (structure only)
199181
actions = build_task1_calwave(sc, project)
200182

201-
# 4) Evaluate (assign vessels/roles + durations)
202-
evaluate_task1(sc, actions)
183+
# 4) Assign (assign vessels/roles)
184+
assign_actions(sc, actions)
203185

204186
# 5) schedule once, in the Task
205187
calwave_task1 = Task.from_scenario(
206188
sc,
207-
strategy='levels', # or 'levels'
189+
name='calwave_task1',
190+
strategy='earliest', # 'earliest' or 'levels'
208191
enforce_resources=False, # keep single-resource blocking if you want it
209192
resource_roles=('vessel', 'carrier', 'operator'))
210-
211-
# 6) build the chart input directly from the Task and plot
193+
194+
# 6) Extract Task1 sequencing info
195+
# calwave_task1.extractSeqYaml()
196+
197+
# 7) update Task1 if needed
198+
calwave_task1.update_from_SeqYaml() # uncomment to re-apply sequencing from YAML
199+
# 8) build the chart input directly from the Task and plot
212200
chart_view = chart.view_from_task(calwave_task1, sc, title='CalWave Task 1 - Anchor installation plan')
213-
chart.plot_task(chart_view)
201+
chart.plot_task(chart_view, outpath='calwave_task1_chart.png')
214202

215203

216204

famodel/irma/calwave_vessels.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ San_Diego:
55
type: crane_barge
66
transport:
77
homeport: national_city
8-
site_distance_m: 41114 # distance to site
8+
route_length_m: 41114 # distance to site
99
cruise_speed_mps: 2.5 # ~5 kts, from doc
1010
Hs_m: 3
1111
station_keeping:
@@ -43,7 +43,7 @@ Jag:
4343
type: tug
4444
transport:
4545
homeport: national_city
46-
site_distance_m: 41114 # distance to site
46+
route_length_m: 41114 # distance to site
4747
cruise_speed_mps: 3.1 #
4848
Hs_m: 3.5
4949
station_keeping:
@@ -70,7 +70,7 @@ Beyster:
7070
type: research_vessel
7171
transport:
7272
homeport: point_loma
73-
site_distance_m: 30558 # distance to site
73+
route_length_m: 30558 # distance to site
7474
cruise_speed_mps: 12.9 # 25 kts cruise, from doc
7575
Hs_m: 2.5
7676
station_keeping:
@@ -109,7 +109,7 @@ Beyster:
109109
# type: research_vessel
110110
# transport:
111111
# homeport: sio_pier
112-
# site_distance_m: 555 # distance to site
112+
# route_length_m: 555 # distance to site
113113
# transit_speed_mps: 10.3 # ~20 kts cruise
114114
# Hs_m: 1.5
115115
# station_keeping:

0 commit comments

Comments
 (0)