BPMN Mining
VI EN
Log in Sign up free
Docs / Formulas

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

TAND=max1 ≤ j ≤ kTj
(2)
CAND=k∑j = 1Cj
(3)

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 condition for the formula to hold
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

🧮 Preparing a delivery — two parallel branches

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:

T1=60+25=85   ·   T2=20

Applying (2) and (3):

TAND=max(85,20)=85 minutes
CAND=(180.000+70.000)+50.000=300.000

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.

How a parallel block is reduced
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.

Anything unclear or missing?