Dynamic Pricing & Recommendation Simulator

Fighting food waste on delivery platforms; PSC January 2020

Simulator Implemented By Koffi Ismael OUATTARA

1 The problem

Food delivery platforms ignore stock levels, generating avoidable waste from unsold dishes. This project proposes joint dynamic pricing + smart recommendations to clear stock without selling at a loss.

2 Methods implemented

Recommendation: Matrix Factorisation & KNN.
Demand: Linear Regression, Logistic Regression, Neural Network & LSTM.
Pricing: MDP with Bellman induction.

3 How to use

Each tab has a method selector at the top. Switch between algorithms to compare their outputs. Hover i icons for parameter definitions, expand the glossary for concepts.

Matrix Factorisation

Ratings matrix M (5 users × 6 dishes) decomposed as U×V. Known entries in bold; italic = predicted. Minimises ‖M−UV‖² + λ‖·‖² via alternating gradient descent.
Latent factors (l) iShared dimension of U (n×l) and V (l×m). Each factor represents a hidden preference criterion (e.g. spiciness). More = expressive but slower. 2
Regularisation λ iRidge penalty: ‖M−UV‖² + λ‖A‖². Prevents overfitting. Set to 0 to see the train/test gap widen. 0.10
Training epochs iPasses over the known ratings. Watch the test curve: when it rises while train loss falls, overfitting has begun. 80
M Rating matrix
n×m matrix where Mi,j is user i’s rating (1–5) for dish j. Missing entries are predicted.
U, V Factor matrices
M ≈ U×V. U (n×l) encodes user tastes; V (l×m) encodes dish characteristics in l hidden dimensions.
λ Ridge penalty
Shrinks U,V coefficients. Reduces overfitting so predictions generalise to unseen pairs.
Anti-waste boost
High-stock dishes get +0.3 score bonus during ranking to steer demand toward items at waste risk.

Training loss

Loss curves.
Recommendations for user
Green badge = high stock. These dishes are ranked up by the anti-waste boost, linking recommendation to the MDP pricing module.

K-Nearest Neighbours (item-based)

For each (user, dish) pair, find the K most similar dishes (by cosine similarity) that the user has already rated. Predicted rating = weighted average of those neighbour ratings.
Neighbours K iNumber of nearest-neighbour items used to form the prediction. Small K = more local (precise but noisy); large K = smoother but may include dissimilar items. For very sparse matrices, K must not exceed the number of rated neighbours. 2
Predict for user iThe user whose unrated dishes we want to predict. The KNN looks at items this user HAS rated to find similar dishes.
Target dish iThe dish we want to predict the rating for. Must be unrated by the selected user. The algorithm finds K dishes most similar to this one that the user has already rated.
Item similarity matrix (cosine)
Cosine similarity
D(A,B) = ⟨A,B⟩ / (‖A‖‖B‖). Measures the angle between two item rating vectors. 1 = identical preference pattern, 0 = orthogonal, −1 = opposite.
Sparsity challenge
If the matrix is 93% empty (as in the Kaggle dataset), many user-item pairs share no common raters, making similarity 0. K must be increased, but distant neighbours degrade quality.
Item-based vs user-based
Item-based KNN (used here) is more stable than user-based because item-item similarities change less frequently than user-user similarities.

Prediction breakdown

Select a user and unrated dish to see the prediction.
K nearest neighbours used
Full predicted matrix (KNN)

MDP State space

States: (stock c, time t). Action: set price p. Bellman backward induction computes V*(s). V(c>0, tmax)=−∞ penalises unsold stock.
Max stock iPortions prepared per evening. Defines the vertical axis of S = {(c,t)}. 10
Demand sensitivity α iDemand prob = exp(−α×p_norm). Higher = more price-sensitive customers. 0.4
Base demand N iScale of the demand function: D(p,t) = N × exp(−α⋅pnorm) × time_factor. Sets the y-axis of the demand chart and expected orders per slot. Match with the N in the Demand Estimation tab for a consistent scenario. 10.0
Price steps N iDiscrete action space size. More steps = finer price control, larger policy table. 5
Time slots (×10 min) iNumber of 10-minute decision steps. Set to 24 = full 4-hour evening service (20:00–24:00). Defines the horizontal axis of the state space. 24
Optimal policy π*(stock, time)

Value function V*(s)

Value function.
Simulated evening service
Starting stock iInitial c₀ at t=0. MDP follows π* with random demand realisations. 8
Revenue
—
Unsold
—
Avg price
—
Run simulation to see pricing decisions…

State space S

A state s=(c, t): c = stock remaining, t = current slot. The MDP finds the optimal price for every possible (c, t) pair.
Action space A — available prices
Terminal reward
V(0, T) = 0 — all sold, no waste
V(c>0, T) = −∞ — unsold stock, penalised
Why waste still occurs: even with this constraint, waste is unavoidable when Σt D(pmin,t) < stock — no price can generate demand that doesn't exist. Uncheck to compare the policy without the anti-waste penalty.

Demand D(p, t) — assumed ground truth

D(p,t) = N × exp(−α⋅pnorm) × (0.3 + 0.7⋅sin(πt/T)). The MDP plans with this exact formula. In practice it must be estimated — see the Full Simulation tab.
Demand chart.

True demand parameters D*(price, slot, day) — the actual customer behaviour all models try to estimate

These sliders define the ground truth. Each demand model below tries to estimate it from data. The gap between model estimate and true demand is the estimation error shown in every chart.
10
1.2
0.70
15%
15%
True demand preview — D*(price tier, time slot) on day 15
True demand preview.

Linear Regression

Demand ~ β₀ + β₁×price + β₂×day_type + β₃×period. Dummy variables encode each category. Interprets aggregate demand drivers.
Price tier iEach price tier is a dummy variable with its own coefficient. Higher price → more negative coefficient → lower predicted demand. Low
Day of month iThree day-type dummies: start (3–9), mid (10–24), end (25–30). End-of-month has positive coefficient (payday effect). 10
Time slot iPeriod dummies: early (0–5), mid (6–17), late (18–23). Late-evening has positive coefficient. 21:10
Demand
—
orders/slot
Revenue
—
€/slot
Elasticity
—
∂D/∂p
Dummy variables
Binary (0/1) indicators for categorical features (price tier, day type, period). Allows the linear model to capture non-continuous category effects.
Price elasticity
∂D/∂p: % demand change per price tier increase. Always negative. Guides MDP reward shaping.
R²
Proportion of variance explained. Linear model captures main effects but misses interactions between price, time, and day.

Demand curve D(price)

Demand curve.

Regression coefficients

Coefficients.

Logistic Regression

Models P(user buys dish) = σ(β₀ + β₁×price + β₂×rating + β₃×wealth). Output is purchase probability per user, enabling individual-level targeting.
Price tier iHigher price shifts the logit downward via the price coefficient (β₁ < 0), reducing P(buy) for all user classes. Low
Predicted rating iRating from the recommendation engine. Positively correlated with P(buy). This is the key link: better recommendation → higher purchase probability → better demand estimate for the MDP. 4
Day type iDay-type shifts the baseline logit. End-of-month = +0.5 (payday effect); mid-month = −0.3.
P(buy) — Rich
—
P(buy) — Mid
—
P(buy) — Poor
—
Sigmoid σ(z)
σ(z)=1/(1+e−z). Maps the linear combination (logit) to a probability in (0,1). The S-shaped curve shows that extreme logit values give near-0 or near-1 probabilities.
Logit
z = β₀ + β₁p + β₂r + β₃class. The raw linear combination before the sigmoid. Positive = likely to buy.
Wealth class effect
Rich users have higher propension à payer: their class coefficient (β₃=+0.48) raises P(buy). Poor class coefficient = −0.59, mid = 0 (reference).
Link to recommendation
The predicted rating from the MF/KNN model enters directly as β₂×rating. Better recommendations → higher purchase probability → more accurate demand estimate.

P(buy) vs price — by wealth class

Logistic curves.

Sigmoid function σ(logit)

Sigmoid curve.

Neural Network (2 hidden layers)

A 2-layer MLP with ReLU activations captures non-linear interactions between price, day, and period that the linear model misses. Inputs: 5 neurons (3 prices + day + period). Output: demand estimate.
Hidden units iNumber of neurons per hidden layer. More = greater capacity to learn complex patterns, but slower convergence and more overfitting risk on small datasets. 16
Learning rate iStep size for gradient descent. Too high = oscillation; too low = slow convergence. Typical range: 0.001–0.01 for Adam, 0.01–0.1 for SGD. 0.005
Training epochs iNumber of passes over the simulated training set (≈288 000 data points in the full scenario). Early stopping when validation loss plateaus. 50
Price tier iInput to the trained network. Compare NN prediction with linear regression to see where they diverge (e.g. at high prices or extreme time slots). Low
NN demand
—
Linear demand
—
Difference
—
ReLU activation
f(x) = max(0,x). Null for negative inputs, linear for positive. Introduces non-linearity while remaining computationally cheap. Used in both hidden layers.
Backpropagation
Gradient of the loss w.r.t. all weights computed via chain rule, then weights updated by gradient descent. Keras minimises cross-entropy loss for classification or MSE for regression.
Universal approximation
A 2-layer network with enough neurons can approximate any continuous function. Here it captures the price×time interaction that eludes the linear model.

Training loss curves

NN loss.

NN vs Linear — demand across price tiers

NN vs linear.

LSTM (Long Short-Term Memory)

Uses the sequence D(t−T), …, D(t−1) to predict D(t). The cell state acts as long-term memory (e.g. remembering that Fridays are busier), while the hidden state carries short-term patterns.
Lookback window T iNumber of past time slots used as input sequence. T=3 means the LSTM sees the last 30 minutes of demand before predicting the next 10-minute slot. Larger T captures longer dependencies. 3
Hidden units iDimension of the hidden state ht and cell state ct. More units = richer memory but more parameters and slower training. 16
Day of month iDay type changes the baseline demand level. The LSTM learns this temporal pattern across the training set and applies it as a seasonal correction. 10
Price tier iPrice is an exogenous input fed into the LSTM at each time step alongside the lagged demand values. Low
Hidden state ht magnitude over evening
Cell memory activity across 24 time slots
Cell state ct
Long-term memory. Updated by the forget gate (what to erase) and input gate (what to write). Can carry information across many time steps without vanishing gradients.
Hidden state ht
Short-term output at each step. Filtered version of ct through the output gate. Used as the prediction basis for the next time slot.
Forget gate ft
σ(Wf[ht−1, xt] + bf). Decides what fraction of ct−1 to keep. Near 0 = forget; near 1 = keep.
Advantage over linear
LSTM captures auto-correlation in demand (busy slot tends to predict next busy slot) and distributional shift across the evening without manual feature engineering.

Demand prediction across evening

LSTM demand.

LSTM vs Linear — cumulative error

Cumulative error.

Platform simulation

Actual sales are driven by true demand D*. The demand model estimates D* for pricing decisions. Waste is unavoidable when Σt D*(pmin,t) < stock — no price generates demand that doesn't exist.
Days iConsecutive evenings. Each day resets stock, runs 24 slots, logs revenue and waste. 7
Initial stock/dish iPortions per dish per evening. Unsold at midnight = waste. 20
Rec anti-waste weight i0% = pure score ranking; 100% = heavy stock-clearing boost. Tune to balance satisfaction vs waste. 40%
Pricing strategy iMDP: uses Bellman π*. Fixed: constant €12. Random: random each slot. Compare to quantify MDP improvement.
Demand model iWhich demand model drives the simulation. NN and LSTM capture non-linear patterns and may produce different revenue/waste trade-offs compared to the linear model.
Revenue chart.

Results

Total revenue
—
Waste %
—
Avg satisfaction
—
Final stock per dish
Event log
Press “Run simulation” to start…

Project overview

PSC at École Polytechnique, January 2020, supervised by Pascal Benchimol. Addresses food waste on delivery platforms by jointly optimising recommendations and dynamic pricing.

Koffi Ismael OuattaraNicolas Huynh Dan BerrebbiLionel Gako
Machine Learning Operations Research Applied Maths Food Tech

Recommendation methods

Matrix Factorisation

Decomposes M into U×V, minimising ‖M−UV‖² + λ‖·‖² via alternating gradient descent. Learns global latent structure.

min ‖M − U×V‖² + λ(‖U‖² + ‖V‖²)

KNN (item-based)

For (user u, item i): find K most similar items (cosine) that u has rated. Predicted = weighted average. Fast prediction but struggles with very sparse matrices (>93% empty).

Demand estimation methods

Linear Regression

Aggregate demand with dummy variables. Interpretable coefficients. Misses price×time interactions.

Logistic Regression

Individual P(buy) = σ(β·x). Includes predicted rating → links to recommendation. Enables wealth-class targeting.

Neural Network (MLP)

2 hidden layers, ReLU. Captures non-linearities. Trained on 288K simulated observations. Compared with linear to show where non-linearity matters.

LSTM

Sequential model using past T demand observations to predict the next slot. Cell state retains evening-long patterns; hidden state drives per-slot predictions. Outperforms MLP on long-horizon temporal dependencies.

Dynamic Pricing (MDP)

Vπ(s) = Σs′[R(s,π(s),s′) + Vπ(s′)]×T(s,π(s),s′)

Terminal: V(c>0, t_max)=−∞. Solved by backward induction. Policy: low price when stock is high and time is short; high price otherwise.

Module coupling

  • Predicted rating → purchase probability (logistic)
  • Ranking position → demand amplification
  • High stock → recommendation boost