Solving the Transporter Problem in an FMCG Company Using the Dynamic Spiral Optimization Algorithm
Mohammad Rizka Fadhli, M.Si.
Market Research Specialist — Nutrifood Research Center, PT Nutrifood Indonesia
Hello!
About Me
Please call me "Ikang Fadhli" — everyone does 😊
Education: Bachelor in Mathematics, Master in Computational Science (ITB)
Day job: Market Research Specialist at Nutrifood — market research, data science, and optimization
I also write a blog about mathematics, market research, and data science → ikanx101.com
Why this topic? Because this is mathematics that is really used every day to make business decisions — not just theory on paper
Today I want to show you the full journey: from a messy real-world problem, to a mathematical model, to an algorithm that runs in production every morning.
Our 60 Minutes Together
Agenda
Nutrifood's distribution problem — why it became a priority project
Data preparation & clustering — cleaning the mess before the math
Dynamic Spiral Optimization — from concept to production
Results and lessons learned — what worked, what hurt
Q&A session — as needed at the end, ask me anything
60 mins
main presentation
Q&A
as needed
3
acts: problem → algorithm → impact
Act 1 · The Real Problem
Nutrifood at a Glance
Since 1979 — a pioneer of healthy FMCG in Indonesia, almost 50 years old
Wide product portfolio: NutriSari — fruit drinksL-Men — high proteinHiLo — high calciumTropicana Slim — low calorieLokalate — coffeeW'dank — traditional drinks
Reach: national — from Sabang to Merauke
Scale: hundreds of SKUs, hundreds of distributors, sub-distributors, and retailers
A business this big has a distribution problem that is not simple at all.
Act 1 · The Real Problem
How Nutrifood Distributes: Direct Shipment
2 manufacturing plants — Ciawi and Cibitung. Both make the same products
Each plant has its own finished-goods warehouse and its own customers — the two customer sets never overlap
"Customer" here means: stores, retailers, distributors, sub-distributors, grosir (wholesalers), HORECA, and small shops (warung)
Every day: hundreds of orders per plant must be assigned to trucks, with fixed schedules
Unlike most FMCG companies, we have no Distribution Center. Every routing decision is made directly at plant level.
Act 1 · The Real Problem
The Transporter Problem: How This Project Started
It all began in 2022 — while I was studying for my Master's in Computational Science at ITB. Perfect timing!
For years, routing and truck scheduling were done manually — Excel, experience, and the "feeling" of our Distribution Officers
These officers had amazing wisdom: which roads jam at which hours, which stores unload slowly, which driver fits which route
But officers resign, retire, or move — and their wisdom leaves with them. No documentation. No system.
Every new officer needed 6–12 months to become efficient. Meanwhile: suboptimal routes, rising transport costs, late deliveries
Management realized: this is not a people problem, it is a system problem. How do we keep the knowledge? Answer: an algorithm.
Act 1 · The Real Problem
The Math Behind It: Vehicle Routing Problem
Simple definition: from one plant, trucks must deliver orders to many stores, on time. Find the cheapest set of routes.
Objective: minimize total transport cost (rental cost per km × all deployed trucks).
The constraints that make it hard:
Truck capacity — weight & volume: no overload, but not too empty either
Time window — each customer has a latest delivery date
Vehicle access — some locations fit a big truck, some only a small van
Every demand must be fulfilled; loading time at the plant counts
Every truck starts and ends at the plant
Assumptions: unlimited rental fleet, and a fixed rental cost per km.
Act 1 · The Real Problem
Why Is This Hard?
A "simple" day looks like this:
Purchase orders (PO) from 100 customers in one day
One PO = one customer, but can contain many products with different volumes (m³) and weights (kg)
Each PO tells us: total cubic & tonnage to send, and a date range for delivery
One PO cannot be split into several shipments
Each customer is served by Ciawi or Cibitung — never both
The number of possible route combinations explodes. No human — and no brute-force computer — can check them all. We need a smart approach.
Act 2 · Dynamic Spiral Optimization
Choosing the Algorithm: Why DSO?
VRP is NP-hard → exact methods are not feasible at this scale → we need a metaheuristic
Honestly: I struggled to write the formal mathematical model, so I could not solve it with library(ompr) like I usually do
Popular choices in industry: Genetic Algorithm (too many parameters), Particle Swarm / bee colony… and Dynamic Spiral Optimization (DSO) — relatively new, simple, easy to explain
The timing was perfect: I was studying Computational Science at ITB. Special credit to Prof. Kuntjoro and Adhe Kania, PhD 🙏
✓ Simple
intuitive math — non-technical teams can follow the logic
✓ DIY
easy to implement in plain R — no heavy libraries
✓ 2 knobs
only rotation angle & contraction rate to tune
Act 2 · Dynamic Spiral Optimization
Nature Discovered It First
Spirals are everywhere: galaxies, snail shells, whirlpools, tornadoes, sunflower patterns (the Fibonacci spiral).
Why a spiral for optimization? A spiral motion rotates (explores new areas — maybe a better route is out there) while it shrinks (focuses on the best route found so far).
Exploration and exploitation happen at the same time — no need to take turns.
Act 2 · Dynamic Spiral Optimization
How DSO Works — In Plain Words
Spread several "agents" randomly on the map
Each agent moves in a spiral toward a center point
The center = the best route found so far
If an agent finds a better route → the center moves to it
Repeat until all agents agree on one point
Very intuitive — you don't need a math degree to understand the idea.
live simulation: agents spiral in, the best point wins
Act 2 · Dynamic Spiral Optimization
DSO in Motion
Act 2 · Dynamic Spiral Optimization
The Algorithm Workflow
1
Initialize — generate N agents at random positions
↓
2
Evaluate — compute the total route cost of each agent
↓
3
Update best — the cheapest route becomes the spiral center
↓
4
Move — all agents spiral around the center
↓
5
Check convergence — agents gathered at the center? → STOP. Else repeat from step 2
Complexity: O(N × D × iterations) — linear. Your infrastructure will not cry.
INPUT
m >= 2 # number of points
theta # rotation angle (0..2π)
r # contraction rate
k_max # max iterations
PROCESS
1 generate m random points x_i
2 set k = 0
3 find x* that gives min f(x*)
4 rotate + contract every x_i
with x* as the center
k = k + 1
5 repeat steps 3 and 4
6 stop when k = k_max
→ output x*
Act 2 · Dynamic Spiral Optimization · the technical one — we'll go fast
The Core Mathematics
The transporter problem is a Mixed Integer Linear Programming (MILP) problem:
minx ∈ ℝnf(x)
subject to gi(x) = 0, i = 1,…,M and hj(x) ≤ 0, j = 1,…,N, x ∈ ℕn
To solve it with DSO, we fold the constraints into one penalized objective:
The rotation matrix is a product of many plane rotations (cross product of matrices)
Why rotation? Rotation does not change the norm of a vector — agents move around the center without "jumping" distances
Once this matrix is written correctly, the rest of the algorithm is surprisingly short.
Act 3 · Implementation Journey · Data 1 of 4
The Data: Customer Orders
One row = one sales order. A customer can have many orders per day. Each order has volume, weight, and a delivery-date window.
id_customer
sales_order
order_kubikasi (m³)
order_kg
tanggal_kirim_min
tanggal_kirim_max
1001
110407120
0.0455640
19.44608
2022-12-05
2022-12-07
1001
110407121
0.0963040
19.54010
2022-12-05
2022-12-07
1001
110407119
0.0451868
12.77226
2022-12-05
2022-12-07
1002
110407122
0.1327040
28.24724
2022-12-05
2022-12-07
1002
110407226
0.0714514
32.11601
2022-12-06
2022-12-06
1003
110407128
0.0093600
6.21600
2022-12-06
2022-12-06
1003
110407127
0.0639600
13.04376
2022-12-06
2022-12-06
1004
193800766
0.0837000
21.18006
2022-12-02
2022-12-03
1004
111172473
12.0024092
1823.70368
2022-12-02
2022-12-03
1005
110407249
1.4551366
699.20512
2022-12-06
2022-12-08
Notice customer 1004: one order of 12 m³ / 1.8 tons next to tiny orders of 0.04 m³ — the size range is huge.
Act 3 · Implementation Journey · Data 2 of 4
The Data: Customer Details
id_customer
long
lat
supplied
max_armada
1001
-6.327437
106.5900
ciawi
2
1002
-6.520842
106.8610
ciawi
2
1003
-6.330330
106.7904
ciawi
7
1004
-7.332009
112.6511
ciawi
7
1005
-6.289716
106.7553
ciawi
2
1006
-6.079036
106.6466
ciawi
7
1008
-6.571628
106.7836
ciawi
5
10011
-6.803916
107.1294
ciawi
2
max_armada = the biggest vehicle type that can physically reach this customer's address
Starting point for Ciawi: −6.649724, 106.843370
War story: we did not have coordinates! The longitude–latitude had to be reconstructed from postal codes. Completing this dataset took a long, long time — far longer than writing the algorithm.
Act 3 · Implementation Journey · Data 3 of 4
The Data: Warehouses
site
week_day_hour
week_end_hour
ciawi
67.5
50
cibitung
81.0
60
Each warehouse has a limited number of working hours for loading trucks — different on weekdays vs weekends
Loading time of every deployed truck consumes this budget
This becomes another constraint: you cannot deploy more trucks than the warehouse can load in a day
Act 3 · Implementation Journey · Data 4 of 4
The Data: Vehicle Fleet
armada
jenis_armada
kubikasi (m³)
max kg
cost / km
max stops
loading (h)
1
Carry
5
1,500
0.76
7
0.6
2
CDE
7
2,000
1.00
6
0.7
3
CDD
12
5,000
1.27
5
1.2
4
CDD Jumbo
17
5,000
1.56
5
1.2
5
FUSO
24
9,000
2.23
5
1.4
6
TRONTON
33
11,000
2.56
5
1.9
7
BU
45
20,000
3.23
5
1.9
8
CONT 20
30
30,000
50.00
2
1.9
9
CONT 40
65
40,000
70.00
2
3.4
Nine vehicle types: from a small Carry (5 m³, cheap, can visit 7 stops) up to a 40-ft container (65 m³, expensive, only 2 stops). Bigger is not always better — cost per km grows fast.
Act 3 · Implementation Journey
What We Want the Algorithm to Produce
Take the order table… and let the algorithm fill three new columns for every sales order:
id_customer
sales_order
order_kubikasi
order_kg
kirim_min
kirim_max
date
truck
stop
1001
110407120
0.0455640
19.44608
12-05
12-07
?
?
?
1001
110407121
0.0963040
19.54010
12-05
12-07
?
?
?
1002
110407122
0.1327040
28.24724
12-05
12-07
?
?
?
1003
110407128
0.0093600
6.21600
12-06
12-06
?
?
?
1004
111172473
12.0024092
1823.70368
12-02
12-03
?
?
?
1005
110407249
1.4551366
699.20512
12-06
12-08
?
?
?
1
Delivery date — which day to ship
2
Vehicle — which truck type carries it
3
Stop order — delivery sequence on the route
Act 3 · Implementation Journey
First Attempt: Brute Elegance
In the first iteration, I let DSO generate the delivery date and the vehicle grouping directly as candidate solutions. The stop order became part of the cost inside the objective function.
The reality check:
DSO works in a continuous space (fractions), but our solutions are discrete (dates, truck IDs)
The matrix dimension was huge — the rotation step ate an enormous amount of computation
Finding a solution took very long, and the result was not optimal
Beautiful on paper. Painful in the server room.
Act 3 · Implementation Journey
First Attempt: The Result Was Far From Optimal
Because the delivery date and vehicle grouping were generated directly by DSO…
…the computer needed a very long time just to reach an acceptable solution — and even that was still far from optimal
The reason: the problem dimension explodes — it depends on how many POs come in that day
Fun fact: last semester, a student team modeled this same problem — and got results very similar to my first iteration. The routes simply did not make sense to Nutrifood's transporter team.
a sample route from the student model — zigzagging back and forth across West Java
Act 3 · Implementation Journey
Second Attempt: The Business Changed the Game
A big change hit the transporter team: their manager retired. A new "head" came in — with new directions and a new business process that reshaped their working procedures
Good news: the delivery date was no longer a decision variable. Why? I was also working on an optimization project with the PPIC team — and one of its outputs is exactly that: the delivery schedule to customers
New rule: if possible, all POs from one customer go on one truck
Bad news: the computation was still heavy. Removing one variable was not enough
We needed a radical change in how we attack the problem — not just a faster computer.
“
There are no big problems — there are just a lot of little problems.
— HENRY FORD
“
What if we don't run DSO on all the data at once? What if we run DSO on small groups instead?
— THE IDEA THAT SAVED THE PROJECT
Act 3 · Implementation Journey
Divide First: Customer Clustering with K-Means
Main idea: don't optimize 200 customers at once — group them by location first (features: latitude & longitude)
Honest confession: my first choice was hierarchical clustering with minimum distance (single linkage) as the cluster rule. But after discussing with the team, K-Means was the method they could accept and understand
Why K-Means won: simple, fast, scales to thousands of customers, easy to explain to the business team
Number of clusters k chosen with the Elbow Method — plus iterations with the wisdom of the transporter team
VRP runs per cluster → solutions come out an order of magnitude faster
The critical trade-off: too many clusters → too many sub-problems; too few → each cluster is still too big. This k is the most important hyperparameter of the whole system.
55 clusters for Site A (Ciawi) — 50 of them on Java island
Act 3 · Implementation Journey
The New Pipeline: Single-Drop vs Multi-Drop
Recap all POs per customer → decide who is single-drop (one dedicated truck) and who is multi-drop (shares a route)
Single-drop test, against the largest vehicle the customer can accept: total tonnage ≤ truck capacity, total volume ≤ truck capacity, and fill ratio > 80%
If a customer's total exceeds the biggest truck → the order is split, and the split itself is optimized with DSO so part of it becomes single-drop
Then DSO solves the VRP on the multi-drop orders and the split leftovers — using library(TSP) in R as the tool to evaluate the cost and distance of every agent. Distances are still Euclidean for now; we are considering real road distances from OpenStreetMap
Act 3 · Implementation Journey
One More Tweak: Squeezing Routes
After all routes are generated, there is one final polish:
Look at every cluster that has more than one route
If two routes pass through the same customer — that's a smell of waste
Run DSO again on those routes, with one goal: squeeze the number of routes down
Fewer trucks doing the same job = direct cost savings. This small tweak pays for itself every single day.
Act 3 · Implementation Journey
Going to Production: A Small OS War
Everything is written in R, developed in RStudio
My problem: as the developer I use Linux and enjoy easy parallel processing…
…but the transporter team works on Windows 😅
The compromise: a dedicated server was built to run the algorithm — 8 cores + 16 GB RAM. The team just triggers it; nobody argues about operating systems anymore
R
one language, end to end
8 cores
parallel DSO runs per cluster
16 GB
RAM on the dedicated server
Act 3 · Implementation Journey
The Daily Pipeline
1
Morning: pull today's sales orders from the system
↓
2
Run the algorithm — about 5 minutes
↓
3
Output: optimal schedule & route per vehicle
↓
4
Feed into NUTREX — the Nutrifood Transporter Expedition app
Tuned DSO parameters
Parameter
Value
Number of agents
15 × number of cores
r (contraction)
0.8
θ (rotation angle)
36°
Max iterations
15
Tuning story: we tried many combinations until we hit this sweet spot. From input to output in ~5 minutes — fast enough for the morning operations.
Act 3 · Implementation Journey
Parameter Sensitivity — in Business Language
r — contraction
🔺 Too big: agents circle forever, slow convergence — the routes don't come out on time
🔻 Too small: shrinks too fast, routes not optimal — money left on the table
🎯 Sweet spot: r ≈ 0.8
θ — rotation angle
🔻 Too small: not enough exploration — only average routes found
🔺 Too big: spins too fast, oscillates, never converges
🎯 Sweet spot: θ = 36°
N — agents
More agents → more accurate results…
…but also longer runtime.
🎯 Find the break-even point for your hardware
Act 3 · Implementation Journey
Actually… There Are Three Algorithms
The same DSO principle powers three variants in production:
🏭
VRP #1
Distributors only Java island
🏪
VRP #2
Retailers only Java island
🚢
VRP #3
Outside Java shipment by sea
Everything I explained today covers VRP #1 and #2. Same spiral, different battlefields.
Act 3 · Implementation Journey
Demo: NUTREX in Action
Act 3 · Implementation Journey
So… How Optimal Is It, Really?
The first and second iterations were not instantly optimal — honest truth
It took a lot of input from the transporter field team, who understand the problem better than any equation
Patching the algorithm + parameter tuning, again and again — that is why it is still in use today
First 3 months: monitoring meetings every 2 weeks, comparing algorithm output vs manual schedules on historical data
The main metric: total cost — manual schedule vs algorithm schedule
½ day
time to build a final VRP — before
5–10 min
time to build a final VRP — now
↓ cost
lower total transport cost vs manual
80%
of daily routes significantly cheaper — one-semester backtest; the rest ≈ equal
Act 3 · Implementation Journey
Lessons Learned
1 · Industry math ≠ campus math
Data is never clean. Business constraints keep changing. Stakeholders don't care which algorithm you use — they care: how much did cost go down?
2 · Clustering is the game-changer
However good DSO is, huge dimensions kill it. 70% of the success came from preprocessing & clustering.
3 · Simple is better
Coding worked even without a formal mathematical formulation. Few parameters → easy maintenance. When people resigned, nobody panicked — the code is simple enough for anyone to continue.
4 · Real-world validation is hard
Benchmarks on paper look great. Production implementation is a completely different sport.
Closing
Conclusions
Nutrifood's Transporter Problem = a real, multi-layer VRP — done manually for years, with officer wisdom lost at every resignation
Clustering reduces dimensionality → one big problem becomes many small, solvable problems
Dynamic Spiral Optimization: a simple metaheuristic — easy to implement, easy to maintain, converges well
Measurable impact: shorter distances, lower transport costs, schedules in minutes instead of half a day
Mathematics is not just theory. It runs every morning at Nutrifood, it moves real trucks, and the knowledge now stays — even when the team changes.