Refined Products Distribution
Batch scheduling, transmix arithmetic, and the Edmonton-to-Burnaby products line
Before You Start
You should know
That a multi-product pipeline can carry different fuels in sequence, but those products partially mix at their boundaries.
You will learn
How batch scheduling works, what transmix is, and why refined-products logistics require inventory and sequencing discipline.
Why this matters
Refined fuel distribution depends on operational precision, not just on total pipeline capacity.
If this gets hard, focus on…
The idea that every product boundary creates a small contaminated zone that has to be managed.
Every litre of gasoline sold in Vancouver left Edmonton as part of a numbered batch in a 24-inch pipe — separated from the next batch of diesel only by the turbulence that inevitably mixes them at the boundary.
That boundary is the operational challenge this essay is about. The crude oil and gas pipelines in P1–P3 each carry a single commodity. The Trans Mountain refined products line carries four or more simultaneously, sequenced one behind another, and the physical fact of turbulent mixing at each interface creates a contaminated zone — transmix — that cannot be delivered as either product without downgrading or reprocessing. Managing transmix is the discipline of multi-product pipeline operation, and the mathematics that describes it is the Cola equation.
A Multi-Product Pipeline Is A Moving Sequence Of Clean Batches Separated By Small Zones Of Contamination
This essay works best when the reader can picture the pipe as a procession rather than a uniform flow. Premium, regular, diesel, and jet fuel all share the same steel cylinder, but each boundary creates transmix, and that forces decisions about minimum batch size, delivery timing, and terminal inventory.
Products Travel In Ordered Slugs
Every interface is a penalty zone. The larger the pipe and the longer the route, the more expensive those mixed boundaries become.
Batch Size Must Be Large Enough To Absorb Interface Loss
Small batch
Responsive to demand, but transmix becomes a big share of the delivered volume.
Large batch
More efficient in-pipe, but slower cycle time and bigger storage needs at the terminal.
Terminal buffer
Receiving tanks smooth the mismatch between pipeline cycle timing and daily consumer demand.
Scheduler’s job
Balance contamination cost, storage limits, and service reliability all at once.
The batch scheduling problem
A multi-product pipeline carries different petroleum products sequentially as discrete batches. The Trans Mountain refined products system moves premium gasoline, regular gasoline, ultra-low sulphur diesel (ULSD), and jet fuel through the same 24-inch pipe from Edmonton’s refinery cluster to the Burnaby Marine Terminal — a distance of approximately 1,150 km.
Each product has tight quality specifications:
| Product | Key specification |
|---|---|
| Premium gasoline (91 octane) | RON ≥ 91, Reid vapour pressure limits |
| Regular gasoline (87 octane) | RON ≥ 87, vapour pressure limits |
| ULSD (diesel) | Sulphur ≤ 15 ppm, cetane index ≥ 40 |
| Jet fuel (Jet A) | Flash point ≥ 38°C, freeze point ≤ −47°C |
At every interface between adjacent batches, turbulent mixing creates a contaminated slug — transmix — that exceeds the specification limits of both products. Transmix is segregated at the destination terminal by detecting the interface (using density meters, chromatographs, or fluorescence sensors) and diverting the off-spec slug to a separate tank. It is subsequently downgraded — typically blended into heavy fuel oil at a discounted price — or returned to a refinery for reprocessing.
Batch size matters because the transmix-to-product ratio determines specification compliance. Too small a batch and the transmix represents an unacceptable fraction of the delivery; too large a batch and cycle times grow, destination terminals require more storage, and responsiveness to demand shifts suffers. The minimum acceptable batch size is typically set at three to five times the predicted transmix volume.
The batch cycle model:
t\_{\text{cycle}} = \frac{V\_{\text{total}}}{Q} \qquad V\_{\text{batch}} \geq V\_{\text{min}}
| Symbol | Meaning | Notes |
|---|---|---|
| tcycle | Time to complete one full product cycle | days |
| Vtotal | Total volume of all batches in one cycle | bbl |
| Q | Pipeline flow rate | bbl/day |
| Vbatch | Volume of individual product batch | bbl |
| Vmin | Minimum acceptable batch size | bbl — rule of thumb: 3–5× transmix volume |
The Cola equation and transmix arithmetic
The Cola equation is an empirical relationship between pipe geometry, route length, and the volume of transmix generated at each batch interface:
Vmix = C ⋅ (D1.9 ⋅ L)0.5
| Symbol | Meaning | Units / value |
|---|---|---|
| Vmix | Transmix volume at the batch interface | barrels |
| C | Empirical constant | 11.75 (field-calibrated; range: 10–14) |
| D | Internal pipe diameter | inches |
| L | Pipe segment length | miles |
The exponent on D is 1.9 — nearly quadratic. This has a counterintuitive implication: a larger diameter pipe generates more transmix per interface, not less. Doubling the diameter increases transmix by approximately 21.9/2 ≈ 1.87 times. A 36-inch pipe doesn’t just carry more product; it generates proportionally more contamination at each batch boundary than a 24-inch pipe. Larger minimum batch sizes are required to compensate.
For the Trans Mountain refined products line (24-inch diameter, 1,150 km ≈ 714 miles):
Vmix = 11.75 × (241.9 × 714)0.5 ≈ 7, 100 bbl
With four products and a safety factor of 4, minimum batch size is 4 × 7, 100 = 28, 400 bbl per product grade. The pipeline cycle time at 150,000 bbl/day throughput is:
t\_{\text{cycle}} = \frac{4 \times 28{,}400}{150{,}000} \approx 0.76 \text{ days}
But actual batch sizes are much larger than the minimum — set by demand volumes and the transit time from Edmonton to Burnaby (~7 days). A product dispatched today arrives in Burnaby in a week; the scheduling must balance in-transit inventory against Burnaby terminal storage capacity.
Edmonton refinery cluster (2024):
| Refinery | Operator | Capacity (bbl/day) |
|---|---|---|
| Strathcona | Imperial Oil (Esso) | ~187,000 |
| Edmonton | Suncor Energy | ~142,000 |
| Redwater | Federated Co-op | ~55,000 |
| Scotford | Shell / CNOOC-Shell JV | ~100,000 |
Total: ~484,000 bbl/day nameplate capacity. Edmonton is the largest refining centre in Canada. Its geographic position — landlocked, served by a single products export pipeline — makes the Trans Mountain refined products line a critical infrastructure link. When that line has a disruption, BC’s fuel supply depends on terminal inventory and alternative supply by marine barge or rail.
The Scheduler Is Really Managing Two Clocks: In-Pipe Transit And On-Site Tank Drawdown
This chapter becomes much easier once readers separate the pipeline clock from the terminal clock. The batch leaves Edmonton now, travels for about a week, then lands in a terminal that has been steadily emptying the whole time.
Dispatch Batch
The scheduler commits pipeline space based on last week’s inventory, today’s nominations, and expected demand one week ahead.
Product Is In Transit
No mid-course correction exists. Burnaby keeps supplying trucks by drawing down stored inventory while the batch is still moving west.
Tank Levels Reset
Operators cut the interface, divert transmix, refill the correct product tank, and immediately begin planning the next cycle.
Terminal inventory design
The Burnaby Marine Terminal must hold enough product to supply British Columbia’s demand continuously despite the 7-day pipeline transit time from Edmonton. Storage capacity sets the response time to demand shifts and the buffer against supply disruptions.
Vstorage = Ddaily ⋅ Tlead + Vsafety
= Ddaily ⋅ (Ttransit + Tsafety)
| Symbol | Meaning | Typical value |
|---|---|---|
| Ddaily | Average daily demand at terminal | bbl/day |
| Ttransit | Pipeline transit time from Edmonton | 7 days |
| Tsafety | Safety stock duration | 7 days (minimum; often 10–14 days) |
| Vstorage | Required terminal storage tank capacity | bbl |
At approximately 110,000 bbl/day total refined products demand for the BC market (distributed across the four product grades), with 7-day transit and 7-day safety stock:
Vstorage = 110, 000 × (7 + 7) = 1, 540, 000 bbl
That is 36+ standard 42,000-barrel storage tanks — a significant infrastructure commitment. Tank capacity at Burnaby is a binding constraint on how quickly the product mix can respond to demand shifts. When jet fuel demand spikes (airline capacity additions, seasonal peaks), terminal storage limits how quickly supply can respond — the pipeline is already full of last week’s batch schedule, and seven days of transit stand between an Edmonton dispatch decision and a Burnaby delivery.
The storage constraint is also the reason why BC fuel prices respond slowly to world oil price changes: the terminal’s product buffer means retailers are drawing down inventory priced at last week’s cost, not today’s refinery margin.
Reference implementation
import numpy as np
MILES_PER_KM = 0.621371
def cola_transmix(diameter_in: float, length_miles: float, C: float = 11.75) -> float:
"""
Cola equation: estimated transmix volume at a batch interface.
Parameters
----------
diameter_in : internal pipe diameter (inches)
length_miles : pipe segment length (miles)
C : empirical constant — default 11.75; range 10–14
Returns
-------
float : transmix volume (barrels)
"""
return C * (diameter_in**1.9 * length_miles)**0.5
def batch_schedule(products: dict, flow_rate_bbl_day: float,
transmix_bbl: float, safety_factor: float = 4.0) -> dict:
"""
Simple batch schedule for a multi-product pipeline.
Parameters
----------
products : dict mapping product name → daily demand (bbl/day)
flow_rate_bbl_day: pipeline throughput (bbl/day)
transmix_bbl : Cola transmix volume at each interface (bbl)
safety_factor : minimum batch = safety_factor × transmix_bbl
Returns
-------
dict with cycle time, batch sizes, and pipeline utilisation
"""
n_products = len(products)
min_batch_bbl = safety_factor * transmix_bbl
total_min_volume = min_batch_bbl * n_products
cycle_time_days = total_min_volume / flow_rate_bbl_day
total_demand = sum(products.values())
batch_sizes = {}
for name, demand in products.items():
proportional = demand * cycle_time_days
batch_sizes[name] = max(proportional, min_batch_bbl)
total_cycle_vol = sum(batch_sizes.values())
actual_cycle_time = total_cycle_vol / flow_rate_bbl_day
return {
"min_batch_bbl": min_batch_bbl,
"transmix_bbl": transmix_bbl,
"n_interfaces": n_products,
"transmix_total_bbl": transmix_bbl * n_products,
"cycle_time_days": actual_cycle_time,
"batch_sizes": batch_sizes,
"total_cycle_vol": total_cycle_vol,
"utilisation": total_demand / flow_rate_bbl_day,
}
def terminal_inventory(daily_demand_bbl: float, transit_days: float,
safety_days: float = 7.0) -> float:
"""Required terminal storage capacity (bbl)."""
return daily_demand_bbl * (transit_days + safety_days)
# ── Reference values — Trans Mountain refined products line ────────────────
# Edmonton → Burnaby, BC: ~1,150 km; D = 24 inches
diameter_in = 24.0
length_km = 1_150.0
length_miles = length_km * MILES_PER_KM
v_mix = cola_transmix(diameter_in, length_miles)
print(f"Trans Mountain refined products line")
print(f" Diameter : {diameter_in:.0f} inches")
print(f" Length : {length_km:.0f} km ({length_miles:.0f} miles)")
print(f" Transmix : {v_mix:,.0f} bbl per interface")
print()
# Product mix — illustrative daily demand into Burnaby terminal
products = {
"Premium gasoline": 12_000, # bbl/day
"Regular gasoline": 45_000,
"Ultra-low sulphur diesel": 38_000,
"Jet fuel": 15_000,
}
flow_rate = 150_000 # bbl/day — Trans Mountain products capacity (approximate)
sched = batch_schedule(products, flow_rate, v_mix, safety_factor=4.0)
print(f"Batch schedule (safety factor = 4× transmix):")
print(f" Min batch size : {sched['min_batch_bbl']:,.0f} bbl")
print(f" Cycle time : {sched['cycle_time_days']:.1f} days")
print(f" Transmix total : {sched['transmix_total_bbl']:,.0f} bbl/cycle ({sched['n_interfaces']} interfaces)")
print(f" Utilisation : {sched['utilisation']*100:.1f}%")
print()
for name, vol in sched["batch_sizes"].items():
print(f" {name:<30}: {vol:>8,.0f} bbl/batch")
print()
# Burnaby terminal storage requirement
total_demand = sum(products.values())
transit_days = 7.0
storage = terminal_inventory(total_demand, transit_days, safety_days=7.0)
print(f"Burnaby terminal storage requirement:")
print(f" Daily demand : {total_demand:,.0f} bbl/day")
print(f" Transit time : {transit_days:.0f} days")
print(f" Safety stock : 7 days")
print(f" Storage needed : {storage:,.0f} bbl ({storage/42_000:.0f} × 42,000-bbl tanks)")Output:
Trans Mountain refined products line
Diameter : 24 inches
Length : 1,150 km (714 miles)
Transmix : 7,049 bbl per interface
Batch schedule (safety factor = 4× transmix):
Min batch size : 28,196 bbl
Cycle time : 0.8 days
Transmix total : 28,196 bbl/cycle (4 interfaces)
Utilisation : 73.3%
Premium gasoline : 28,196 bbl/batch
Regular gasoline : 28,196 bbl/batch
Ultra-low sulphur diesel : 28,196 bbl/batch
Jet fuel : 28,196 bbl/batch
Burnaby terminal storage requirement:
Daily demand : 110,000 bbl/day
Transit time : 7 days
Safety stock : 7 days
Storage needed : 1,540,000 bbl (37 × 42,000-bbl tanks)
Run it yourself
The cell below lets you explore transmix sensitivity to pipe geometry and the effect of product count on cycle time. The most counterintuitive result is the diameter scaling: increase diameter_in from 24 to 30 and watch transmix grow despite the larger pipe — that is the D1.9 exponent at work. Increasing the number of products from four to six adds two interfaces, grows minimum cycle time by 50%, and requires larger terminal storage to buffer the longer batch cycle.
import numpy as np
MILES_PER_KM = 0.621371
# ── Parameters — change these ──────────────────────────────────────────────
diameter_in = 24.0 # pipe diameter (inches) — try: 16–36
length_km = 1_150.0 # route length (km) — Edmonton→Burnaby: 1150; try: 500–2000
flow_rate_bblday = 150_000 # pipeline capacity (bbl/day) — try: 80_000–200_000
safety_factor = 4.0 # minimum batch = N × transmix — try: 3–6
n_products = 4 # number of product grades in the schedule — try: 3–6
transit_days = 7.0 # pipeline transit time (days) — try: 3–12
daily_demand = 110_000 # total daily demand at destination terminal (bbl/day)
# ── Transmix calculation ───────────────────────────────────────────────────
length_miles = length_km * MILES_PER_KM
v_mix = 11.75 * (diameter_in**1.9 * length_miles)**0.5
# ── Batch schedule ─────────────────────────────────────────────────────────
min_batch = safety_factor * v_mix
total_min_vol = min_batch * n_products
cycle_days = total_min_vol / flow_rate_bblday
transmix_total = v_mix * n_products
utilisation = daily_demand / flow_rate_bblday
# ── Terminal storage ───────────────────────────────────────────────────────
storage_bbl = daily_demand * (transit_days + 7.0) # transit + 7-day safety stock
# ── Output ─────────────────────────────────────────────────────────────────
print(f"Pipeline geometry:")
print(f" {diameter_in:.0f}-inch pipe, {length_km:.0f} km ({length_miles:.0f} miles)")
print()
print(f"Transmix per interface : {v_mix:,.0f} bbl")
print(f"Total transmix/cycle : {transmix_total:,.0f} bbl ({n_products} interfaces)")
print(f"Min batch size : {min_batch:,.0f} bbl ({safety_factor:.0f}× transmix)")
print(f"Cycle time : {cycle_days:.1f} days ({n_products} products)")
print()
print(f"Pipeline utilisation : {utilisation*100:.1f}%")
print(f"Terminal storage needed : {storage_bbl:,.0f} bbl")
print(f" ({storage_bbl/42_000:.0f} × 42,000-bbl tanks)")
print()
print("Transmix sensitivity to pipe diameter:")
for d in [16, 20, 24, 30, 36]:
vm = 11.75 * (d**1.9 * length_miles)**0.5
print(f" D={d:2d} in: transmix = {vm:>6,.0f} bbl")
Where next?
Each of the four commodity systems — crude oil, NGL, natural gas, refined products — can be modelled in isolation using the equations developed in P1 through P4. But Alberta’s energy economy operates all four simultaneously, and the systems interact: NGL volumes depend on gas production, diluent volumes depend on bitumen output, refined products volumes depend on refinery utilisation. P5 takes all four systems as a single directed graph and asks the overarching question: what is the total export capacity of Alberta’s energy network, and where is the binding constraint?