All work

Delayed Coker Yield Optimisation

Diagnosing post-COVID model drift and building a hybrid ML + evolutionary optimiser to lift refinery yield by ~2%.

Impact at a glance

↑ ~56 points

Model accuracy (R²), from negative to positive

~2%

Yield improvement

Problem

Built during COVID-era refinery turndowns, the client's yield prediction model collapsed once normal operations resumed—scoring worse on recent data than simply guessing the historical average. Simply retraining on fresh data failed. My objective was threefold: uncover why the model was blind to the new operating regime, rebuild it for both accuracy and physical explainability, and use it to recommend optimal operating settings.

To understand why operating regimes shift, it helps to see what happens inside the unit:

How a Delayed Coker Works

DELAYED COKING / PROCESS CYCLE00 / 06
STEAMING & WATER RECOVERYREACTION & COKE CUTTINGPRODUCT FRACTIONATIONResidual oil feedHot feed140-bar cutting waterGasCoker NaphthaLight Gas OilHeavy Gas OilReflux pumpQuench tower: Cools Drum 1’s steam and vapours before de-coking.Quench towerSeparator: Separates condensed water and oil from offgas.SeparatorDe-coking derrick 1: Supports the high-pressure water lance that cuts solid coke.Derrick 1Coke Drum 1: Hot residual oil cracks into vapour and solid coke at 3–8 bar.Drum 13–8 barDe-coking derrick 2: Supports the high-pressure water lance that cuts solid coke.Derrick 2Coke Drum 2: Hot residual oil cracks into vapour and solid coke at 3–8 bar.Drum 23–8 bar3-way switch valve: Diverts hot feed between the two coke drums.Switch valveFurnace: Heats the fractionator bottoms to 480–482 °C.Furnace480–482 °CMain fractionator: Separates drum vapours into products at different boiling ranges.MainfractionatorReflux drum: Collects condensed overhead liquid and supplies naphtha and reflux.Reflux drumSidecut stripper: Strips lighter components from the light gas oil side stream.SidecutstripperCoolers: Cooling water removes heat from overhead vapours and gas oil products.CondenserFeed pump: Pumps residual oil into the bottom of the main fractionator.Reflux pump: Returns part of the condensed naphtha to the fractionator.Coke haulers: Collect the solid coke cut from the drums for transport to storage.Coke haulers → storageDrain sump: Collects cutting water draining from the wet coke.Drain sumpRecycle pump: Returns recovered cutting water to the de-coking water circuit.
WaterOil / hydrocarbonNaphthaHot feed
Dual-drum operation · 3–8 bar · 480–482 °C

0. Full-colour overview

The complete process: water in blue, hydrocarbons in purple, naphtha in pink, and hot feed in red.

Confidentiality & Proxy Walkthrough

To protect client IP, the technical deep-dive below demonstrates the exact same mathematical pipeline on an open engineering benchmark (UCI Concrete Compressive Strength).

Refinery Operating Temperatures & PressuresConcrete Ingredient Mix (Water, Cement)
Coker Liquid Yield (%)28-Day Compressive Strength (MPa)

Solution

The fix came in three parts: repair the evaluation, rebuild the model, then optimise on top of it.

1

Fix the Evaluation Split

Stop regime blindness by replacing sequential splits with stratified time-block sampling.

2

Build a Hybrid Model

Pair Ridge Regression (global physical trends) with LightGBM (local nonlinear residuals).

3

Optimise with Constraints

Run Differential Evolution across the discontinuous model surface to find peak settings safely.

Act I: Fixing a Blind Evaluation Split

Before touching the algorithm, I audited how the data was split. The model wasn't failing because the math was bad—it was failing because it had never seen the current operating regime during training.

  • Diagnosis: The legacy 90:10 sequential split placed all recent post-COVID months into the test set, leaving the training set completely blind to the new operating regime.
  • Fix: Implemented block-stratified sampling—training on alternating 2-month blocks and testing on the 1-month blocks in between so every operating regime is represented without temporal leakage.
  • Outcome: R² jumped ~56 points (from negative back to positive) and RMSE dropped by ~17%.
Sequential versus stratified training splitTraining dataTest dataNewest operating regimeBefore: sequential 90:10 splitTest = newest months only. Training neversaw the shifted operating regime.After: stratified blocksacross the whole periodEvery regime, including the newest, appearsin training and test. Blocks never overlap.time →

Illustrative schematic, not client data.

Act II: Combining Physical Trends with Nonlinear Corrections

With an honest evaluation baseline in place, we needed a model that refinery engineers could trust. Pure tree models capture complex quirks but fail to extrapolate clean physical trends; linear models capture trends but miss local interactions.

The hybrid modelThe hybrid model1Ridge draws the trendfridge(x) = β0 + β1x1+ β2x2 + ⋯ + βnxn2What Ridge missedr = y − fridge(x)3LightGBM learns the missesg(x) ≈ r4Final predictionŷ = fridge(x) + g(x)

The hybrid model: Ridge for the trend, LightGBM for what it missed.

Explore the interactive sandbox below to see how Ridge establishes the baseline trend first, leaving LightGBM to learn only the leftover residuals:

Watch the hybrid model get built

A toy example with made-up data, to show the idea. Not the concrete model.

data & prediction
● training○ hold-out— Ridge trend— LightGBM on residuals— final hybrid
  1. 1Ridge draws the trend

    f_ridge(x) = β₀ + β₁·x

  2. 2What Ridge missed

    r = y − f_ridge(x)

  3. 3LightGBM learns the misses

    g(x) ≈ r

  4. 4Final prediction

    ŷ = f_ridge(x) + g(x)

Step 1 of 4: Ridge draws the trend. Hold-out RMSE: Ridge 3.04, hybrid 2.39.

Ridge captures the trend. LightGBM only has to learn what is left over. Add the two and you get a model that is accurate and still easy to explain.

On the open benchmark, the engineered Hybrid model beat all five alternatives (Test R² 0.915, RMSE 4.71 MPa)—delivering gradient-boosted accuracy while retaining a clear linear backbone.

Model comparison: RMSE in MPa, lower is betterModel comparisonRMSE in MPa, lower is better5-fold CV RMSE (train)Test RMSE (held out)02468Hybrid (engineered)3.844.71LightGBM (engineered)3.994.75Hybrid (raw)4.134.96LightGBM (raw)4.154.69Linear (engineered)6.446.88Linear (raw)8.008.20

Model comparison on the open concrete dataset (RMSE, lower is better).

Act III: Searching a Stepped Surface with Differential Evolution

Predicting yield is only half the battle—operators need recommended setpoints. Because LightGBM partitions data into discrete splits, the hybrid model's response surface is full of flat steps where gradient-based optimisers get stuck. Differential Evolution (DE) solves this by evolving a population of candidate recipes across the landscape without needing derivatives.

Differential evolutionDifferential evolution1Mutate: combine three candidatesv = a + F × (b − c)2Crossover: blend with the originalu = mix(v, x; CR)3Select: keep the better onex ← better of (x, u)a, b, c = three random candidatesF = step size · CR = how much to swap

The three moves DE repeats every generation.

Interactive Concept

How Differential Evolution escapes local traps:

Differential Evolution on a test function (Rastrigin)

A toy problem to show how the search works. Not the concrete model.

y = +5.12Rastrigin · f(x, y)
−5.12x+5.12
Population ○● BestDark = low · bright = high

Best value · log scale

1e21e01e-21e-41e-61e-8050100150
Generation
0 / 150
Best value
9.9782
Best position (x, y)
(-1.0682, 1.1933)
Distance to (0, 0)
1.60151

Global minimum: f(0, 0) = 0

Generation 0: best 9.9782 · Paused

Current best result at generation 0: value 9.978212755802094, position -1.0681560349464416, 1.1932668161392213, distance from the true minimum 1.6015127253604755.

Each generation, DE mixes three points to propose a new one and keeps it only if it is better. Over time, the whole population collapses onto the best region.

Act IV: Recommending the Optimal Settings (and Reality-Checking Them)

Applying DE directly to our trained Hybrid Model, each candidate represents a physical recipe (water and cement bounds). The optimiser searches for the highest predicted strength while respecting real-world mass and ratio constraints.

Example: finding the strongest concrete mixExample: finding the strongestconcrete mixInputs: w = water, c = cement (kg/m³)Output: strength (MPa)1Ridge: the straight-line trendfridge(w, c) = β0 + β1w+ β2c2Hybrid: trend + LightGBM’s fixŷ(w, c) = fridge(w, c) + g(w, c)3Goal: the mix with highest strength(w, c) = arg max ŷ(w, c)w, c ∈ bounds4DE’s score: flip the signJ(w, c) = −ŷ(w, c)Each DE candidate is one mix (w, c).DE minimises: lowest J = strongest mix.Bounds: min to max seen in the training data.

How the search is scored: the hybrid model predicts each mix's strength, and DE minimises the sign-flipped prediction.

Watch the optimiser search

The real run: Differential Evolution searching cement and water for the strongest concrete mix. The surface is the hybrid model's predicted strength.

Generation 0: best — · Search complete

Each dot is a candidate recipe. The red marker is the strongest mix found so far. Watch the population spread out, then collapse onto the best region.

Outcome & Engineering Sanity Check

The Recommendation

86.1 MPa Predicted Peak

Up from the 78.8 MPa maximum observed in historical data (mirroring the ~2% yield lift achieved on the live refinery coker).

The Reality Check

73.6 MPa Cross-Check

Optimisers love to exploit model blind spots. When cross-checked against an independent model, the same recipe scored 73.6 MPa—proving why ML recommendations must be treated as bounded candidates for physical validation, not blind autopilot settings.

Key takeaways

01

Fix the evaluation before the model. A leaky split hid a failing model behind healthy-looking scores.

02

Use a simple model for the trend and a flexible one for the corrections: accurate and still explainable.

03

Optimisers exploit model blind spots. Constrain them, cross-check them, and validate before acting.

Tech stack

Ridge RegressionLightGBMDifferential EvolutionPython