Parallel block (AND)
Time takes the max, cost takes the sum.
The parallel block is where time and cost part ways. Get this wrong and the whole problem is wrong.
The formulas
With k branches running at the same time; Tj and Cj are the time and cost of the whole of branch j (a branch may itself contain several steps, and even contain a nested parallel block).
Why time takes the max and cost takes the sum
Three teams work for 20, 45 and 10 minutes at the same time. The clock runs only once for all three — you wait exactly 45 minutes. But the payroll pays all three, and the invoice adds them up. Time is a resource that can be shared; money is not.
The formula assumes the branches really do run in parallel, that is, there are enough people/machines to work simultaneously. If all three branches are done by one person one after another, that is a sequential chain disguised as parallel work — redraw it as a sequence, otherwise the time will be undercounted.
Example
Branch 1: Pick the goods (60 minutes / 180.000) → Pack (25 minutes / 70.000)
Branch 2: Prepare the shipping documents (20 minutes / 50.000)
The time of each branch:
Applying (2) and (3):
Making the document branch 15 minutes faster changes nothing: it is not on the critical path.
How the tool finds the boundaries of a block
A parallel block starts at the split gateway and ends at the join gateway — the nearest step that every branch passes through. Once the boundaries are known, the whole block is reduced to one synthetic node carrying the max time and the summed cost, and the rest of the graph is solved as usual.
synthetic = f"__par__{block.split}"
graph.time[synthetic] = graph.time[block.split] + max(branch_times) # (2)
graph.cost[synthetic] = graph.cost[block.split] + sum(branch_costs) # (3)
# Mọi luồng đang trỏ vào cổng rẽ nay trỏ vào node tổng hợp
for source, targets in graph.transitions.items():
if block.split in targets:
targets[synthetic] = targets.pop(block.split)
graph.transitions[synthetic] = {block.join: 1.0}
Blocks nested inside blocks are handled by reducing them from the inside out — see Composing nested blocks.