Nutrifood
ITB
IMW 2026 · Summer Course · ITB Ganesha · 13 July 2026

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

  1. Nutrifood's distribution problem — why it became a priority project

  2. Data preparation & clustering — cleaning the mess before the math

  3. Dynamic Spiral Optimization — from concept to production

  4. Results and lessons learned — what worked, what hurt

  5. 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 drinks L-Men — high protein HiLo — high calcium Tropicana Slim — low calorie Lokalate — coffee W'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.

Nutrifood brands
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.

Plant Ciawi + own warehouse Plant Cibitung + own warehouse Distributor Sub-distributor Grosir Retailer HORECA Warung Distributor Sub-distributor Grosir Retailer HORECA Warung direct shipment — no DC
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.

PLANT one plant, many routes, many rules
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.

Truck fleet
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

  1. Spread several "agents" randomly on the map

  2. Each agent moves in a spiral toward a center point

  3. The center = the best route found so far

  4. If an agent finds a better route → the center moves to it

  5. 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 ∈ ℝn f(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:

F(x, α, β) = f(x) + Σi=1..M αi gi2(x) + Σj=1..N βj ( max(hj(x), 0) )2

where α, β are penalty constants that can be made very large. Break a rule → pay a huge penalty.

What matters is the logic behind this — not memorizing the formula.

Act 2 · Dynamic Spiral Optimization

The One Tricky Part: Rotation in n Dimensions

DSO is easy to code in any language — except for one stone in your shoe: defining the rotation matrix for an n-dimensional problem.

R(n)1,2, θ1,3, …, θn,n−1) = ∏i=1..n−1 ( ∏j=1..i R(n)ni, n+1−jni, n+1−j) )
  • 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_customersales_orderorder_kubikasi (m³)order_kgtanggal_kirim_mintanggal_kirim_max
10011104071200.045564019.446082022-12-052022-12-07
10011104071210.096304019.540102022-12-052022-12-07
10011104071190.045186812.772262022-12-052022-12-07
10021104071220.132704028.247242022-12-052022-12-07
10021104072260.071451432.116012022-12-062022-12-06
10031104071280.00936006.216002022-12-062022-12-06
10031104071270.063960013.043762022-12-062022-12-06
10041938007660.083700021.180062022-12-022022-12-03
100411117247312.00240921823.703682022-12-022022-12-03
10051104072491.4551366699.205122022-12-062022-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_customerlonglatsuppliedmax_armada
1001-6.327437106.5900ciawi2
1002-6.520842106.8610ciawi2
1003-6.330330106.7904ciawi7
1004-7.332009112.6511ciawi7
1005-6.289716106.7553ciawi2
1006-6.079036106.6466ciawi7
1008-6.571628106.7836ciawi5
10011-6.803916107.1294ciawi2
  • 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

siteweek_day_hourweek_end_hour
ciawi67.550
cibitung81.060
  • 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

armadajenis_armadakubikasi (m³)max kgcost / kmmax stopsloading (h)
1Carry51,5000.7670.6
2CDE72,0001.0060.7
3CDD125,0001.2751.2
4CDD Jumbo175,0001.5651.2
5FUSO249,0002.2351.4
6TRONTON3311,0002.5651.9
7BU4520,0003.2351.9
8CONT 203030,00050.0021.9
9CONT 406540,00070.0023.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_customersales_orderorder_kubikasiorder_kgkirim_minkirim_maxdatetruckstop
10011104071200.045564019.4460812-0512-07???
10011104071210.096304019.5401012-0512-07???
10021104071220.132704028.2472412-0512-07???
10031104071280.00936006.2160012-0612-06???
100411117247312.00240921823.7036812-0212-03???
10051104072491.4551366699.2051212-0612-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.

Sample route from the student model

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

  1. 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

  2. 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

  3. New rule: if possible, all POs from one customer go on one truck

  4. 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.

Customer clusters map

55 clusters for Site A (Ciawi) — 50 of them on Java island

Act 3 · Implementation Journey

The New Pipeline: Single-Drop vs Multi-Drop

  1. Recap all POs per customer → decide who is single-drop (one dedicated truck) and who is multi-drop (shares a route)

  2. Single-drop test, against the largest vehicle the customer can accept: total tonnage ≤ truck capacity, total volume ≤ truck capacity, and fill ratio > 80%

  3. 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

  4. 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.

same customer! before: 2 routes overlap after DSO: 1 squeezed route
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

ParameterValue
Number of agents15 × number of cores
r (contraction)0.8
θ (rotation angle)36°
Max iterations15

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.

Nutrifood
ITB

Thank You! 🙏

Let's talk — Q&A starts now.

Mohammad Rizka Fadhli, M.Si.

✉️ rizka.fadhli@nutrifood.co.id  ·  ikanx101@gmail.com

🌐 ikanx101.com

Act 1 · The Real Problem
1 / 36

Ready to present 🎬

Swipe left / right  or  press

Tap anywhere to start