Français · English
Solveur et étude numérique des politiques conjointes de tarification et de production d'un système make-to-stock à capacité limitée, dont la demande est modulée par une chaîne de Markov.
Le projet reproduit puis étend les résultats de Gayon, Talay-Değirmenci, Karaesmen et Örmeci (2009), Optimal Pricing and Production Policies of a Make-to-Stock System with Fluctuating Demand, Probability in the Engineering and Informational Sciences, 23(2), 205-230.
Un fabricant produit un article unique, le stocke, et le vend à une clientèle sensible au prix. Sa capacité de production est bornée (un serveur unique traitant une unité à la fois) et la demande alterne entre plusieurs régimes qu'il observe sans les contrôler.
Il dispose de deux leviers : le niveau de stock jusqu'auquel produire, et le prix affiché. La question posée est celle du second :
Jusqu'où faut-il complexifier une politique de prix, et quel gain en attendre ?
Trois degrés de sophistication sont envisageables, du prix unique au prix recalculé à chaque mouvement de stock, chacun plus coûteux à exploiter que le précédent. L'étude quantifie le gain de chacun et détermine où s'arrêter.
L'état est le couple
| Composante | Spécification |
|---|---|
| Production | serveur exponentiel de taux |
| Environnement | chaîne de Markov à |
| Demande | processus de Poisson modulé, intensité |
| Coûts | stockage |
Après uniformisation, c'est-à-dire sous la normalisation
l'équation d'optimalité en gain moyen s'écrit
où
dont le maximiseur admet une forme fermée pour la demande linéaire.
Le prix et le base-stock constituent deux axes de liberté indépendants, ce qui donne cinq politiques et non trois. Tous les gains rapportés le sont relativement à S.
| Prix unique | Un prix par régime | Un prix par état | |
|---|---|---|---|
| Base-stock unique | S | SB | non considérée |
| Un base-stock par régime | SP | EDP | DP |
Quatre propriétés du papier sont vérifiées numériquement sur chaque instance résolue :
la politique de production optimale est un base-stock dépendant du régime (Théorème 1) ;
le prix DP décroît avec le stock (Théorème 2) ; les base-stocks sont ordonnés comme les
régimes sous la Condition 1 (Théorème 3) ; et le prix DP franchit
Le solveur repose sur l'itération sur les politiques. À prix fixés, la politique
optimale est entièrement décrite par
puis relit les seuils dans la fonction de valeur. La convergence demande 5 à 8 tours.
La value iteration, employée initialement, ne convenait pas : avec
Un second solveur, indépendant, calcule le même gain moyen par la loi stationnaire du générateur en temps continu :
Les deux méthodes concordent à la précision machine, ce qui valide simultanément l'uniformisation, la normalisation du gain et la construction du générateur.
Deux noyaux dominent le temps de calcul et sont portés en Rust : le balayage de prix de la
stratégie EDP, dont le coût croît en
Le levier principal n'est pas le langage mais la structure. La matrice d'évaluation
| Python | Rust | ||
|---|---|---|---|
| Balayage EDP, |
47,2 s | 0,8 s | x59 |
| Balayage EDP, |
35,4 s | 1,2 s | x29 |
| Seuil commun SB, |
89,7 s | 3,8 s | x23 |
solve_all, |
90,6 s | 5,5 s | x16 |
solve_all, |
environ 40 min | 104 s | x23 |
| Validation complète contre le papier | 135 s | 16,5 s | x8 |
Le moteur Python demeure la référence : c'est lui qui est validé contre le papier, et le
portage n'hérite de cette validation que par les tests d'équivalence de
tests/test_native.py. En l'absence du module compilé, le solveur retombe silencieusement
sur Python.
Périmètre :
| Élément | Résultat |
|---|---|
| Table 4, base-stocks optimaux | 20 valeurs sur 20 exactes |
| Table 3, profit gains | écart maximum de 0,14 point de pourcentage |
| Table 5, prix S / SB / SP / EDP | toutes les valeurs exactes |
| Table 2, menu de prix | les 15 valeurs concordent |
| Figure 1, gain maximal mono-environnement | 3,80 % contre 3,81 % publié |
| Figure 2, |
3,22 % contre 3,24 % publié |
La Table 4 constitue le test le plus discriminant : les base-stocks étant des entiers, aucun écart ne peut s'y dissimuler derrière un arrondi. Les valeurs de la Figure 1 et de la Figure 2 proviennent de balayages indépendants et n'ont jamais servi de cible.
Trois volets dépassent le périmètre de la source et ne disposent d'aucune référence publiée. Ils sont vérifiés par des cas limites, ce qui constitue une garantie sensiblement plus faible.
| Extension | Cas limite | Écart |
|---|---|---|
|
|
un régime dédoublé dans un modèle à 3 états reproduit le cas à 2 états | nul |
| Coût de mise en route |
|
nul |
| Backlog |
|
La reproduction fait apparaître trois points sur lesquels les valeurs ou le texte publiés diffèrent des résultats du solveur, ou d'eux-mêmes. Leur interprétation reste à clarifier auprès des auteurs. Les trois sont figés dans la suite de tests, de sorte qu'une évolution du solveur les ferait apparaître plutôt que de les dissimuler dans une tolérance élargie.
- Le paragraphe 5.3.2 observe, « dans toutes les expériences », que
$s(\varphi,e) \leq s(\text{dp},e)$ pour$\varphi \in {\text{s}, \text{sb}, \text{sp}, \text{edp}}$ . La Table 4 du même article publie pourtant$s(\text{sb}) = 4$ et$s(\text{dp},L) = 3$ à$\varepsilon = 0{,}8$ . Le solveur reproduit ces deux valeurs exactement : l'écart ne vient pas du calcul. La propriété n'est naturelle que pour SP et EDP, dont le base-stock dépend du régime ; S et SB n'en ont qu'un, compromis entre les régimes, qui n'a pas de raison de rester sous le seuil retenu par DP dans le régime le plus faible. - Table 5,
$\varepsilon = 0{,}6$ :$p(\text{dp},L)$ publié à 0,33, calculé à 0,31. La valeur 0,33 figure aussi dans la version de conférence de 2006. Le solveur applique la définition du papier (minimum sur les états récurrents, atteint au stock 22) ; 0,33 correspond au prix du stock 20, mais ce choix de fenêtre contredirait les autres colonnes de la même ligne. L'écart reste inexpliqué. - Table 5,
$\varepsilon = 0{,}8$ :$p(\text{dp},H)$ publié à 0,99, calculé à 0,88. Ici la source diverge d'une version à l'autre : la version de conférence de 2006 (mêmes paramètres) publie 0,88, que le solveur retrouve (0,883). La version de revue publie 0,99 et son texte donne un écart de prix de DP de 0,8, cohérent avec 0,99 : ce n'est donc pas une simple coquille de tableau, et on ignore si elle provient d'un nouveau calcul ou d'une erreur propagée. Le solveur ne retrouve 0,99 nulle part : le prix maximal de DP reste à 0,883 pour toute variabilité de 0,7 à 0,95, et le résultat est insensible à la troncature de l'espace d'état comme à la tolérance numérique.
Sans fluctuation de la demande, la tarification dynamique ne rapporte presque rien.
En régime unique, S, SB, SP et EDP se confondent, et le gain de DP mesure exactement
l'apport de la dépendance au niveau de stock. Le maximum atteignable sur l'ensemble du plan
Avec fluctuation, l'ordre de grandeur change, mais pas là où on l'attend. Au point de
référence (
Le classement de SP et SB s'inverse avec la capacité. À capacité contrainte
(
Deux ou trois tarifs suffisent, dans le cadre étudié. Pour le système à un seul régime
de la Table 2 (
Python 3.11 ou plus récent (testé sous 3.12).
pip install -e ".[dashboard,notebooks,dev]" # ou : pip install -r requirements.txtL'installation en mode éditable (-e) est requise : le solveur retrouve le moteur natif et
les résultats dans rust/ et report/, à la racine du dépôt. Le cœur du solveur ne dépend
que de numpy ; les extras dashboard, notebooks et dev ajoutent le reste.
Le moteur natif est optionnel et requiert une chaîne Rust :
cd rust && cargo build --releaseAucune autre étape n'est nécessaire : dynamicpricer/native.py détecte l'artefact et le solveur
l'utilise automatiquement.
Pour utiliser un moteur compilé ailleurs (autre machine, conteneur, artefact de CI), pointez la
variable d'environnement DYNAMICPRICER_NATIVE_DIR vers un dossier target/ de cargo ou vers
le dossier contenant directement la bibliothèque. Elle est prioritaire sur rust/target/ ; si
elle ne contient rien d'utilisable, un avertissement est émis et le solveur retombe sur le
moteur Python (mêmes résultats, plus lent).
Tableau de bord interactif :
streamlit run dashboard/app.pyL'interface est disponible en français et en anglais : un sélecteur en haut de la barre latérale change la langue sans perdre les paramètres ni les résultats déjà calculés. Par défaut, la langue du navigateur est utilisée.
Curseurs sur
Tests unitaires et d'intégration :
pytestValidation contre les valeurs publiées, plus lente et donc exclue par défaut :
pytest -m slowRecalcul des résultats alimentant les notebooks :
python -m dynamicpricer.validation
python -m dynamicpricer.sensitivitydynamicpricer/
├── src/dynamicpricer/ # le paquet Python
│ ├── params.py # dataclass Problem, uniformisation, demande linéaire
│ ├── mdp/ # solveur en gain moyen
│ │ ├── operators.py # opérateurs de Bellman
│ │ ├── policies.py # extraction des seuils base-stock
│ │ ├── value_iteration.py # solve_mdp (instance unique)
│ │ ├── batch.py # solve_batch, average_profit (grille de prix)
│ │ └── dynamic_pricing.py # itération sur les politiques pour DP
│ ├── strategies.py # les cinq stratégies S, SB, SP, EDP, DP
│ ├── metrics.py # profit gains, états récurrents, contrôles de cohérence
│ ├── validation.py # comparaison aux tables publiées
│ ├── sensitivity.py # balayages de µ, h, ε, q et menus de prix
│ ├── native.py # chargement optionnel du moteur Rust
│ ├── ui_texts.py # textes du tableau de bord (français / anglais)
│ └── reference_gayon2009.py # valeurs publiées, transcrites de la source
├── rust/ # moteur natif : balayage de prix et seuil commun
├── notebooks/
│ ├── 01_validation.ipynb # reproduction des Tables 2-5 et des Figures 1-2
│ └── 02_sensitivity.ipynb # analyses de sensibilité et enseignements
├── dashboard/app.py
├── tests/
├── requirements.txt
└── report/ # rapport HTML autonome, résultats calculés (JSON)
Du modèle. Ni le coût de mise en œuvre d'une politique, ni la réaction des clients aux variations de prix ne sont facturés. Les gains affichés pour la tarification dynamique sont donc des majorants, et son avantage réel sur la tarification par régime est plus étroit que ces chiffres. Le modèle suppose par ailleurs le régime parfaitement observable au moment de décider, hypothèse favorable qui majore elle aussi le gain.
De la validation. Seul le cœur du modèle est validé contre une source publiée. Les trois extensions ne le sont que par cohérence interne : elles sont cohérentes avec elles-mêmes, ce qui n'établit pas leur correction au regard d'une référence externe.
Du coût de mise en route.
Du coût de calcul. Le balayage de prix croît en
Gayon, J.-P., Talay-Değirmenci, I., Karaesmen, F., Örmeci, E. L. (2009). Optimal Pricing and Production Policies of a Make-to-Stock System with Fluctuating Demand. Probability in the Engineering and Informational Sciences, 23(2), 205-230.
Code publié sous licence MIT (voir LICENSE). Les valeurs publiées de Gayon et al. (2009)
reprises dans src/dynamicpricer/reference_gayon2009.py restent attribuées à leurs auteurs ; les articles
eux-mêmes ne sont pas redistribués.