Skip to content

Commit bd13d26

Browse files
committed
Updating duration, actions_ti, and other features in Task.py based on the sequence graph:
- sequence graph simplified (without labels) and with a title that describes the actions that are done by each checkpoint (and/or end) node. - sequence graph calculates the total duration based on the thickest edge (largest duration) between the checkpoint nodes. - assigns the duration back to task self while also populating actions_ti relative starting time of each action in the self.
1 parent 9018f79 commit bd13d26

File tree

2 files changed

+77
-62
lines changed

2 files changed

+77
-62
lines changed

famodel/irma/irma.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -450,10 +450,6 @@ def implementStrategy_staged(sc):
450450
act_sequence[acts[i].name] = []
451451
else: # remaining actions are just a linear sequence
452452
act_sequence[acts[i].name] = [ acts[i-1].name ] # (previous action must be done first)
453-
# Just for testing different examples.
454-
# act_sequence = {'install_anchor-fowt0a': [],
455-
# 'install_anchor-fowt0b': ['install_anchor-fowt0a'],
456-
# 'install_anchor-fowt0c': ['install_anchor-fowt0a']}
457453
sc.addTask(acts, act_sequence, 'install_all_anchors')
458454

459455
# ----- Create a Task for all the mooring installs -----

famodel/irma/task.py

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,11 @@ def __init__(self, actions, action_sequence, name, **kwargs):
5959
self.actions[act.name] = act
6060

6161

62-
# Create a graph of the sequence of actions in this task based on action_sequence
63-
self.getSequenceGraph(action_sequence)
6462

6563
self.name = name
6664

6765
self.status = 0 # 0, waiting; 1=running; 2=finished
68-
6966
self.actions_ti = {} # relative start time of each action [h]
70-
self.t_actions = {} # timing of task's actions, relative to t1 [h]
71-
# t_actions is a dict with keys same as action names, and entries of [t1, t2]
7267

7368
self.duration = 0 # duration must be calculated based on lengths of actions
7469
self.cost = 0 # cost must be calculated based on the cost of individual actions.
@@ -77,9 +72,14 @@ def __init__(self, actions, action_sequence, name, **kwargs):
7772

7873
# what else do we need to initialize the task?
7974

80-
# internal graph of the actions within this task.
81-
self.G = self.getTaskGraph()
82-
75+
# Create a graph of the sequence of actions in this task based on action_sequence
76+
self.getSequenceGraph(action_sequence, plot=True) # this also updates duration
77+
78+
self.cost = sum(action.cost for action in self.actions.values())
79+
0
80+
print(f"---------------------- Initializing Task '{self.name} ----------------------")
81+
print(f"Task '{self.name}' initialized with duration = {self.duration:.2f} h.")
82+
print(f"Task '{self.name}' initialized with cost = ${self.cost:.2f} ")
8383

8484
def organizeActions(self):
8585
'''Organizes the actions to be done by this task into the proper order
@@ -103,7 +103,7 @@ def calcDuration(self):
103103

104104

105105

106-
def getSequenceGraph(self, action_sequence):
106+
def getSequenceGraph(self, action_sequence, plot=True):
107107
'''Generate a multi-directed graph that visalizes action sequencing within the task.
108108
Build a MultiDiGraph with nodes:
109109
Start -> CP1 -> CP2 -> ... -> End
@@ -164,7 +164,7 @@ def level_of(a: str, b: set[str]) -> int:
164164

165165
pos = nx.shell_layout(H, nlist=shells)
166166

167-
xmin, xmax = -2.0, 2.0
167+
xmin, xmax = -2.0, 2.0 # maybe would need to change those later on.
168168
pos["Start"] = (xmin, 0)
169169
pos["End"] = (xmax, 0)
170170

@@ -186,57 +186,76 @@ def level_of(a: str, b: set[str]) -> int:
186186
else: # lv == max_level
187187
H.add_edge(f"CP{num_cps}", "End", key=action, duration=action.duration, cost=action.cost)
188188

189-
fig, ax = plt.subplots()
190-
# pos = nx.shell_layout(G)
191-
nx.draw(H, pos, with_labels=True, node_size=500, node_color="lightblue", edge_color='white')
192-
193-
# Group edges by unique (u, v) pairs
194-
for (u, v) in set((u, v) for u, v, _ in H.edges(keys=True)):
195-
# get all edges between u and v (dict keyed by edge key)
196-
edge_dict = H.get_edge_data(u, v) # {key: {attrdict}, ...}
197-
n = len(edge_dict)
198189

199-
# curvature values spread between -0.3 and +0.3
200-
if n==1:
201-
rads = [0]
202-
else:
203-
rads = np.linspace(-0.3, 0.3, n)
204-
205-
# draw each edge
206-
durations = [d.get("duration", 0.0) for d in edge_dict.values()]
207-
scale = max(max(durations), 0.0001)
208-
width_scale = 4.0 / scale # normalize largest to ~4px
209-
210-
for rad, (k, d) in zip(rads, edge_dict.items()):
211-
nx.draw_networkx_edges(
212-
H, pos, edgelist=[(u, v)], ax=ax,
213-
connectionstyle=f"arc3,rad={rad}",
214-
arrows=True, arrowstyle="-|>",
215-
edge_color="gray",
216-
width=max(0.5, d.get("duration", []) * width_scale),
217-
)
218-
219-
# --- after drawing edges ---
220-
edge_labels = {}
221-
for u, v, k, d in H.edges(keys=True, data=True):
222-
# each edge may have a unique key; include it in the label if desired
223-
label = k.name
224-
edge_labels[(u, v, k)] = label
225-
226-
nx.draw_networkx_edge_labels(
227-
H,
228-
pos,
229-
edge_labels=edge_labels,
230-
font_size=8,
231-
label_pos=0.5, # position along edge (0=start, 0.5=middle, 1=end)
232-
rotate=False # keep labels horizontal
233-
)
234-
235-
ax.axis("off")
236-
plt.tight_layout()
237-
plt.show()
190+
# 3. Compute cumulative start time for each level
191+
level_groups = {}
192+
for action, lv in levels.items():
193+
level_groups.setdefault(lv, []).append(action)
238194

195+
level_durations = {lv: max(self.actions[a].duration for a in acts)
196+
for lv, acts in level_groups.items()}
197+
198+
task_duration = sum(level_durations.values())
199+
200+
level_start_time = {}
201+
elapsed = 0.0
202+
cp_string = []
203+
for lv in range(1, max_level + 1):
204+
level_start_time[lv] = elapsed
205+
elapsed += level_durations.get(lv, 0.0)
206+
# also collect all actions at this level for title
207+
acts = [a for a, l in levels.items() if l == lv]
208+
if acts and lv <= num_cps:
209+
cp_string.append(f"CP{lv}: {', '.join(acts)}")
210+
elif acts and lv > num_cps:
211+
cp_string.append(f"End: {', '.join(acts)}")
212+
213+
# Assign to self:
214+
self.duration = task_duration
215+
self.actions_ti = {a: level_start_time[lv] for a, lv in levels.items()}
239216
self.sequence_graph = H
217+
218+
title_str = f"Task {self.name}. Duration {self.duration:.2f} : " + " | ".join(cp_string)
219+
220+
if plot:
221+
fig, ax = plt.subplots()
222+
# pos = nx.shell_layout(G)
223+
nx.draw(H, pos, with_labels=True, node_size=500, node_color="lightblue", edge_color='white')
224+
225+
label_positions = {} # to store label positions for each edge
226+
# Group edges by unique (u, v) pairs
227+
for (u, v) in set((u, v) for u, v, _ in H.edges(keys=True)):
228+
# get all edges between u and v (dict keyed by edge key)
229+
edge_dict = H.get_edge_data(u, v) # {key: {attrdict}, ...}
230+
n = len(edge_dict)
231+
232+
# curvature values spread between -0.3 and +0.3 [helpful to visualize multiple edges]
233+
if n==1:
234+
rads = [0]
235+
offsets = [0.5]
236+
else:
237+
rads = np.linspace(-0.3, 0.3, n)
238+
offsets = np.linspace(0.2, 0.8, n)
239+
240+
# draw each edge
241+
durations = [d.get("duration", 0.0) for d in edge_dict.values()]
242+
scale = max(max(durations), 0.0001) # avoid div by zero
243+
width_scale = 4.0 / scale # normalize largest to ~4px
244+
245+
for rad, offset, (k, d) in zip(rads, offsets, edge_dict.items()):
246+
nx.draw_networkx_edges(
247+
H, pos, edgelist=[(u, v)], ax=ax,
248+
connectionstyle=f"arc3,rad={rad}",
249+
arrows=True, arrowstyle="-|>",
250+
edge_color="gray",
251+
width=max(0.5, d.get("duration", []) * width_scale),
252+
)
253+
label_positions[(u, v, k)] = offset # store position for edge label
254+
255+
ax.set_title(title_str, fontsize=12, fontweight="bold")
256+
ax.axis("off")
257+
plt.tight_layout()
258+
240259
return H
241260

242261

0 commit comments

Comments
 (0)