{"id":48946904,"url":"https://github.com/ai-vnv/deepbullwhip","last_synced_at":"2026-04-17T17:07:40.108Z","repository":{"id":350297572,"uuid":"1206228843","full_name":"ai-vnv/deepbullwhip","owner":"ai-vnv","description":"Multi-tier supply chain bullwhip effect simulator","archived":false,"fork":false,"pushed_at":"2026-04-09T18:28:21.000Z","size":5996,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-09T19:37:58.676Z","etag":null,"topics":["bullwhip-effect","inventory-management","monte-carlo","newsvendor","python","semiconductor","simulation","supply-chain"],"latest_commit_sha":null,"homepage":null,"language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ai-vnv.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-09T17:51:02.000Z","updated_at":"2026-04-09T18:23:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ai-vnv/deepbullwhip","commit_stats":null,"previous_names":["ai-vnv/deepbullwhip"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ai-vnv/deepbullwhip","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-vnv%2Fdeepbullwhip","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-vnv%2Fdeepbullwhip/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-vnv%2Fdeepbullwhip/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-vnv%2Fdeepbullwhip/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ai-vnv","download_url":"https://codeload.github.com/ai-vnv/deepbullwhip/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ai-vnv%2Fdeepbullwhip/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31699441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T21:17:31.016Z","status":"ssl_error","status_checked_at":"2026-04-11T21:17:24.556Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bullwhip-effect","inventory-management","monte-carlo","newsvendor","python","semiconductor","simulation","supply-chain"],"created_at":"2026-04-17T17:07:36.247Z","updated_at":"2026-04-17T17:07:40.094Z","avatar_url":"https://github.com/ai-vnv.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DeepBullwhip\n\n[![CI](https://github.com/ai-vnv/deepbullwhip/actions/workflows/ci.yml/badge.svg)](https://github.com/ai-vnv/deepbullwhip/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/ai-vnv/deepbullwhip/graph/badge.svg)](https://codecov.io/gh/ai-vnv/deepbullwhip)\n[![Docs](https://img.shields.io/badge/docs-ai--vnv.github.io%2Fdeepbullwhip-006747)](https://ai-vnv.github.io/deepbullwhip)\n[![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://github.com/ai-vnv/deepbullwhip)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Version](https://img.shields.io/badge/version-0.4.1-orange)](https://github.com/ai-vnv/deepbullwhip/releases)\n\n**Multi-tier supply chain bullwhip effect simulator with modular demand models, ordering policies, and cost functions.**\n\nMaintained by the [AI Verification \u0026 Validation (AI V\u0026V) Lab](https://ai-vnv.kfupm.io) at King Fahd University of Petroleum \u0026 Minerals (KFUPM).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/summary_dashboard.png\" alt=\"DeepBullwhip Summary Dashboard\" width=\"700\"\u003e\n\u003c/p\u003e\n\n---\n\n## Overview\n\nDeepBullwhip provides a configurable simulation framework for studying the\n[bullwhip effect](https://en.wikipedia.org/wiki/Bullwhip_effect) in serial\nsupply chains. It is designed for researchers and practitioners who need to:\n\n- Simulate multi-echelon supply chains under different demand patterns\n- Model arbitrary DAG supply chain topologies (serial, tree, convergent/divergent)\n- Compare ordering policies (Order-Up-To, custom policies) and cost structures\n- Quantify bullwhip amplification, fill rates, and total supply chain costs\n- Optimize inventory levels and policy parameters using mathematical programming\n- Generate publication-grade diagnostic visualizations (matplotlib + Graphviz)\n- Run Monte Carlo experiments to study forecast-accuracy vs. robustness tradeoffs\n- Integrate with the Python ecosystem: NetworkX, Graphviz, Pyomo\n\nThe package is extracted from a computational study on the accuracy–robustness\ntradeoff in ML-driven semiconductor supply chains (see `simulation.ipynb`).\n\n## Features\n\n| Component | Description |\n|-----------|-------------|\n| **Demand generators** | Pluggable via `DemandGenerator` ABC. Built-in: AR(1) semiconductor, Beer Game step, ARMA(p,q), Replay from data |\n| **Ordering policies** | Pluggable via `OrderingPolicy` ABC. Built-in: OUT, Proportional OUT, Smoothing OUT, Constant Order |\n| **Cost functions** | Pluggable via `CostFunction` ABC. Built-in: Newsvendor (h+b), Perishable (h+b+obsolescence) |\n| **Forecasters** | Pluggable via `Forecaster` ABC. Built-in: Naive, Moving Average, Exponential Smoothing, DeepAR (GluonTS) |\n| **Benchmarking** | `BenchmarkRunner` for standardized policy/forecaster comparison with LaTeX/CSV export |\n| **Datasets** | Built-in datasets: Beer Game, WSTS semiconductor, synthetic AR(1)/ARMA, M5 Walmart |\n| **Registry** | Decorator-based `@register` system for easy extensibility and model discovery |\n| **Supply chain** | `SerialSupplyChain` supporting arbitrary K-echelon serial topologies via `EchelonConfig` |\n| **Network topologies** | `SupplyChainGraph` + `NetworkSupplyChain` for arbitrary DAG supply chains (trees, convergent/divergent) |\n| **NetworkX integration** | Bidirectional graph conversion, critical path analysis, centrality, topological ordering |\n| **Graphviz visualization** | Publication-quality SVG/PDF network rendering with metrics overlay |\n| **Pyomo optimization** | Inventory optimization, policy parameter tuning, network design (MIP) |\n| **Diagnostics** | 10 publication-grade plot functions + network diagram + geographic map visualization |\n| **Metrics** | BWR, NSAmp, Fill Rate, Total Cost, Chen lower bound (standalone module + backward-compat diagnostics) |\n| **Vectorized engine** | `VectorizedSupplyChain` — matrix-based `(N, K, T)` simulation for Monte Carlo batching. **~100x speedup** over serial for N=1000 paths |\n\n## Installation\n\n```bash\n# Install from PyPI\npip install deepbullwhip\n\n# With all optional dependencies\npip install deepbullwhip[all]\n```\n\nFor development:\n\n```bash\ngit clone https://github.com/ai-vnv/deepbullwhip.git\ncd deepbullwhip\npip install -e \".[dev]\"\n```\n\n### Dependencies\n\n- **Core:** numpy, scipy, pandas, matplotlib\n- **Dev:** pytest, pytest-cov\n- **Optional (Network):** networkx (`pip install deepbullwhip[network]`)\n- **Optional (Viz):** graphviz (`pip install deepbullwhip[viz]`)\n- **Optional (Optimize):** pyomo (`pip install deepbullwhip[optimize]`)\n- **Optional (ML):** scikit-learn, torch, gluonts (`pip install deepbullwhip[ml]`)\n- **Optional (Benchmark):** kaggle, tabulate\n- **All optional:** `pip install deepbullwhip[all]`\n\n\u003e **Apple Silicon (MPS) note:** GluonTS/PyTorch Lightning may fail on M1/M2/M3 Macs\n\u003e when the MPS backend is auto-selected. Set `PYTORCH_ENABLE_MPS_FALLBACK=1` before\n\u003e running DeepAR training or benchmarks:\n\u003e ```bash\n\u003e export PYTORCH_ENABLE_MPS_FALLBACK=1\n\u003e python benchmarks/run_leaderboard.py\n\u003e ```\n\u003e The CAIE experiment scripts set this automatically.\n\n## Quick Start\n\n```python\nimport numpy as np\nfrom deepbullwhip import (\n    SemiconductorDemandGenerator,\n    SerialSupplyChain,\n)\n\n# 1. Generate demand (156 weeks, with shock at week 104)\ngen = SemiconductorDemandGenerator()\ndemand = gen.generate(T=156, seed=42)\n\n# 2. Simulate the default 4-echelon semiconductor supply chain\nchain = SerialSupplyChain()\nforecasts_mean = np.full_like(demand, demand.mean())\nforecasts_std = np.full_like(demand, demand.std())\nresult = chain.simulate(demand, forecasts_mean, forecasts_std)\n\n# 3. Inspect results\nfor k, er in enumerate(result.echelon_results):\n    print(f\"E{k+1}: {er.name:12s}  BW={er.bullwhip_ratio:.2f}  \"\n          f\"FR={er.fill_rate:.0%}  Cost={er.total_cost:,.0f}\")\n```\n\n## Benchmarking (v0.2.0)\n\nCompare ordering policies and forecasting methods in a single call:\n\n```python\nfrom deepbullwhip.benchmark import BenchmarkRunner\n\nrunner = BenchmarkRunner(\n    chain_config=\"semiconductor_4tier\",  # or \"beer_game\", \"consumer_2tier\"\n    demand=\"semiconductor_ar1\",          # or \"beer_game\", \"arma\"\n    T=156, N=100, seed=42,\n)\n\n# Compare policies\nresults = runner.run(\n    policies=[\n        \"order_up_to\",\n        (\"proportional_out\", {\"alpha\": 0.3}),\n        (\"constant_order\", {\"order_quantity\": 11.6}),\n    ],\n    forecasters=[\"naive\", (\"moving_average\", {\"window\": 10})],\n    metrics=[\"BWR\", \"FILL_RATE\", \"TC\"],\n)\n\n# View results\nprint(results.pivot_table(index=[\"policy\",\"echelon\"], columns=\"metric\", values=\"value\"))\n\n# Export\nrunner.export_csv(results, \"benchmark_results.csv\")\nrunner.export_latex(results, \"benchmark_table.tex\", caption=\"Policy Comparison\")\n```\n\n### Adding Custom Models\n\nExtend the framework with the 3-step pattern:\n\n```python\nfrom deepbullwhip.policy.base import OrderingPolicy\nfrom deepbullwhip.registry import register\n\n@register(\"policy\", \"my_policy\")\nclass MyPolicy(OrderingPolicy):\n    def __init__(self, lead_time: int, service_level: float = 0.95):\n        self.lead_time = lead_time\n    def compute_order(self, inventory_position, forecast_mean, forecast_std):\n        return max(0.0, forecast_mean * 1.5 - inventory_position)\n\n# Now use it in benchmarks:\nresults = runner.run(policies=[\"order_up_to\", \"my_policy\"])\n```\n\nSee [Notebook 03: Custom Policies](notebooks/03_custom_policies.ipynb) for a full walkthrough.\n\n### Real-World Dataset Benchmarks\n\nRun benchmarks on well-known demand datasets out of the box:\n\n```python\nfrom deepbullwhip.datasets.loader import load_dataset\nfrom deepbullwhip.demand.replay import ReplayDemandGenerator\n\n# Load M5 Walmart, Australian PBS, WSTS, or Beer Game\ndemand = load_dataset(\"m5\", store=\"CA_1\", dept=\"FOODS_1\", freq=\"weekly\")\n\nrunner = BenchmarkRunner(\n    chain_config=\"consumer_2tier\",\n    demand=ReplayDemandGenerator(data=demand),\n    T=200, N=10, seed=42,\n)\nresults = runner.run(policies=[\"order_up_to\", (\"proportional_out\", {\"alpha\": 0.3})])\n```\n\n| Dataset | Source | Frequency | Periods |\n|---------|--------|-----------|---------|\n| M5 Walmart | Kaggle M5 Competition | Weekly | 277 |\n| Australian PBS | tidyverts/tsibbledata | Monthly | 197 |\n| WSTS Semiconductor | Bundled sample | Monthly | 60 |\n| Beer Game | Built-in | Weekly | 52 |\n\nDownload scripts for each dataset are in `data/raw/*/download.sh`.\nSee [`notebooks/08_benchmark_real_datasets.ipynb`](notebooks/08_benchmark_real_datasets.ipynb) for a cross-dataset comparison.\n\n## Network Topologies (v0.3.0)\n\nModel arbitrary DAG supply chains beyond serial chains:\n\n```python\nfrom deepbullwhip import SupplyChainGraph, EdgeConfig, NetworkSupplyChain, EchelonConfig\nimport numpy as np\n\n# Define a distribution tree: Factory -\u003e Warehouse -\u003e {Retail_A, Retail_B}\ngraph = SupplyChainGraph(\n    nodes={\n        \"Factory\": EchelonConfig(\"Factory\", lead_time=4, holding_cost=0.10, backorder_cost=0.40),\n        \"Warehouse\": EchelonConfig(\"Warehouse\", lead_time=2, holding_cost=0.15, backorder_cost=0.50),\n        \"Retail_A\": EchelonConfig(\"Retail_A\", lead_time=1, holding_cost=0.20, backorder_cost=0.60),\n        \"Retail_B\": EchelonConfig(\"Retail_B\", lead_time=1, holding_cost=0.20, backorder_cost=0.60),\n    },\n    edges={\n        (\"Factory\", \"Warehouse\"): EdgeConfig(lead_time=3),\n        (\"Warehouse\", \"Retail_A\"): EdgeConfig(lead_time=1),\n        (\"Warehouse\", \"Retail_B\"): EdgeConfig(lead_time=1),\n    },\n)\n\n# Simulate\nchain = NetworkSupplyChain(graph)\nT = 52\nresult = chain.simulate(\n    demand={\"Retail_A\": np.full(T, 5.0), \"Retail_B\": np.full(T, 3.0)},\n    forecasts_mean={\"Retail_A\": np.full(T, 5.0), \"Retail_B\": np.full(T, 3.0)},\n    forecasts_std={\"Retail_A\": np.full(T, 1.0), \"Retail_B\": np.full(T, 1.0)},\n)\n\nfor name, er in result.node_results.items():\n    print(f\"{name:12s}  BW={er.bullwhip_ratio:.2f}  FR={er.fill_rate:.0%}\")\n```\n\n### NetworkX Integration\n\n```python\nfrom deepbullwhip import to_networkx, from_networkx\nfrom deepbullwhip.network import find_critical_path, echelon_centrality\n\n# Convert to NetworkX for graph analysis\nG = to_networkx(graph)\nprint(\"Critical path:\", find_critical_path(G))\nprint(\"Centrality:\", echelon_centrality(G))\n\n# Build from NetworkX\nimport networkx as nx\nG = nx.DiGraph()\nG.add_node(\"Supplier\", lead_time=4, holding_cost=0.1, backorder_cost=0.4)\nG.add_node(\"Store\", lead_time=1, holding_cost=0.2, backorder_cost=0.6)\nG.add_edge(\"Supplier\", \"Store\", lead_time=2)\nchain = NetworkSupplyChain.from_networkx(G)\n```\n\n### Graphviz Visualization\n\n```python\nfrom deepbullwhip import render_network, save_figure\n\n# Render network diagram (with optional simulation overlay)\nsource = render_network(graph, sim_result=result, engine=\"dot\", title=\"Distribution Tree\")\nsave_figure(source, \"network.pdf\")\n```\n\n### Pyomo Optimization\n\n```python\nfrom deepbullwhip.optimize import tune_service_levels, tune_smoothing_factors\n\n# Find optimal service levels via simulation-optimization\nscenarios = np.random.default_rng(42).normal(10, 2, (50, 52))\nscenarios = np.maximum(scenarios, 0)\n\nresult = tune_service_levels(graph, scenarios, objective=\"total_cost\")\nprint(\"Optimal service levels:\", result.parameters)\nprint(\"Expected cost:\", result.objective_value)\n\n# Find optimal smoothing factors\nresult = tune_smoothing_factors(graph, scenarios)\nprint(\"Optimal alpha_s:\", result.parameters)\n```\n\n## Standardized Schema + Multi-Backend Rendering (v0.3.0)\n\nDefine supply chains in a standard JSON format and render identically across matplotlib, Graphviz, and TikZ:\n\n### JSON Schema\n\n```json\n{\n  \"version\": \"1.0\",\n  \"metadata\": {\"name\": \"Consumer 2-Tier\", \"tags\": [\"serial\", \"2-echelon\"]},\n  \"nodes\": [\n    {\"id\": \"Manufacturer\", \"config\": {\"lead_time\": 4, \"holding_cost\": 0.10, \"backorder_cost\": 0.40},\n     \"layout\": {\"tier\": 0, \"role\": \"manufacturer\"}},\n    {\"id\": \"Retailer\", \"config\": {\"lead_time\": 1, \"holding_cost\": 0.20, \"backorder_cost\": 0.80},\n     \"layout\": {\"tier\": 1, \"role\": \"retailer\"}}\n  ],\n  \"edges\": [{\"source\": \"Manufacturer\", \"target\": \"Retailer\", \"config\": {\"lead_time\": 3}}]\n}\n```\n\n### Multi-Backend Rendering\n\n```python\nfrom deepbullwhip import render_graph, from_serial, to_json, save_json, load_json\nfrom deepbullwhip.chain.config import beer_game_config\n\ngraph = from_serial(beer_game_config())\n\n# Save to standard JSON\nsave_json(graph, \"beer_game.json\", metadata={\"name\": \"Beer Game\"})\n\n# Render with matplotlib (default) — 4 built-in themes\nfig = render_graph(graph, theme=\"kfupm\")            # KFUPM green/gold (default)\nfig = render_graph(graph, theme=\"ieee\")              # IEEE grayscale, 3.5\" width\nfig = render_graph(graph, theme=\"presentation\")      # Large fonts for slides\nfig = render_graph(graph, theme=\"minimal\")           # Clean black \u0026 white\n\n# Render as TikZ for LaTeX papers\ntex = render_graph(graph, backend=\"tikz\", theme=\"ieee\", title=\"Beer Game\")\nwith open(\"beer_game.tex\", \"w\") as f:\n    f.write(tex)\n\n# Render with Graphviz (requires pip install deepbullwhip[viz])\nsource = render_graph(graph, backend=\"graphviz\", engine=\"dot\")\n\n# One-liner: load JSON and render\nfig = render_from_json(\"beer_game.json\", theme=\"kfupm\")\n```\n\n### Supply Chain Examples (Different Tier Counts)\n\n**2-Tier (Manufacturer → Retailer):**\n```python\nfrom deepbullwhip.chain.config import consumer_2tier_config\nfig = render_graph(from_serial(consumer_2tier_config()), theme=\"minimal\")\n```\n\n**4-Tier Beer Game (Factory → Distributor → Wholesaler → Retailer):**\n```python\nfrom deepbullwhip.chain.config import beer_game_config\nfig = render_graph(from_serial(beer_game_config()), theme=\"kfupm\", title=\"MIT Beer Game\")\n```\n\n**Distribution Tree (Factory → Warehouse → {Store A, Store B}):**\n```python\nfrom deepbullwhip import SupplyChainGraph, EdgeConfig, EchelonConfig, render_graph\n\ntree = SupplyChainGraph(\n    nodes={\n        \"Factory\": EchelonConfig(\"Factory\", 4, 0.10, 0.40),\n        \"Warehouse\": EchelonConfig(\"Warehouse\", 2, 0.15, 0.50),\n        \"Store_A\": EchelonConfig(\"Store_A\", 1, 0.20, 0.60),\n        \"Store_B\": EchelonConfig(\"Store_B\", 1, 0.20, 0.60),\n    },\n    edges={\n        (\"Factory\", \"Warehouse\"): EdgeConfig(lead_time=3),\n        (\"Warehouse\", \"Store_A\"): EdgeConfig(lead_time=1),\n        (\"Warehouse\", \"Store_B\"): EdgeConfig(lead_time=1),\n    },\n)\nfig = render_graph(tree, theme=\"presentation\", title=\"Distribution Network\")\ntex = render_graph(tree, backend=\"tikz\", theme=\"ieee\")  # For LaTeX papers\n```\n\n## Benchmark Leaderboard\n\nSee the [Benchmark Leaderboard](https://ai-vnv.github.io/deepbullwhip/LEADERBOARD/) on the docs site\n([source](docs/LEADERBOARD.md)) for the latest benchmark results across all\nregistered forecasters, policies, and demand generators.\n\nRun it yourself:\n\n```bash\npython benchmarks/run_leaderboard.py\n```\n\n## Contributing a New Component\n\ndeepbullwhip uses a registry architecture — adding a new forecaster, policy,\nor demand generator requires no changes to existing code.\n\n### Adding a Forecaster\n\n**1. Implement the Forecaster ABC**\n\nCreate a file `deepbullwhip/forecast/my_forecaster.py`:\n\n```python\nimport numpy as np\nfrom deepbullwhip.forecast.base import Forecaster\nfrom deepbullwhip.registry import register\n\n@register(\"forecaster\", \"my_forecaster\")\nclass MyForecaster(Forecaster):\n    \"\"\"One-line description.\"\"\"\n\n    def __init__(self, my_param: float = 1.0):\n        self.my_param = my_param\n\n    def forecast(\n        self, demand_history: np.ndarray, steps_ahead: int = 1\n    ) -\u003e tuple[float, float]:\n        # Your logic here — must return (mean, std)\n        mean = float(np.mean(demand_history[-10:]))\n        std = float(np.std(demand_history[-10:]))\n        return mean, std\n```\n\nKey points:\n- Must inherit from `Forecaster`\n- Must use `@register(\"forecaster\", \"name\")` decorator\n- Must implement `forecast(demand_history, steps_ahead) -\u003e (mean, std)`\n- Optionally override `generate_forecasts(demand)` for batch efficiency\n- If it has heavy dependencies (torch, gluonts, etc.), guard imports inside methods\n\n**2. Register in `__init__.py`**\n\nAdd to `deepbullwhip/forecast/__init__.py`:\n\n```python\n# For lightweight dependencies:\nfrom deepbullwhip.forecast.my_forecaster import MyForecaster\n\n# For heavy/optional dependencies:\ntry:\n    from deepbullwhip.forecast.my_forecaster import MyForecaster\nexcept ImportError:\n    pass  # requires optional dependency X\n```\n\n**3. Test locally**\n\n```python\nfrom deepbullwhip.benchmark import BenchmarkRunner\n\nrunner = BenchmarkRunner(\"semiconductor_4tier\", \"semiconductor_ar1\", T=156, N=200, seed=42)\nresults = runner.run(\n    policies=[\"order_up_to\"],\n    forecasters=[\"naive\", \"my_forecaster\"],\n    metrics=[\"BWR\", \"CUM_BWR\", \"FILL_RATE\", \"TC\"],\n)\nprint(results.pivot_table(\n    index=[\"forecaster\", \"echelon\"],\n    columns=\"metric\", values=\"value\",\n).to_string(float_format=\"%.1f\"))\n```\n\n**4. Run the official benchmark**\n\n```bash\npython benchmarks/run_leaderboard.py\n```\n\nInclude the output in your PR description.\n\n### Adding a Policy\n\nSame pattern — implement `OrderingPolicy` ABC:\n\n```python\nfrom deepbullwhip.policy.base import OrderingPolicy\nfrom deepbullwhip.registry import register\n\n@register(\"policy\", \"my_policy\")\nclass MyPolicy(OrderingPolicy):\n    def compute_order(self, inventory_position, forecast_mean, forecast_std) -\u003e float:\n        ...\n```\n\n### Adding a Demand Generator\n\nSame pattern — implement `DemandGenerator` ABC:\n\n```python\nimport numpy as np\nfrom deepbullwhip.demand.base import DemandGenerator\nfrom deepbullwhip.registry import register\n\n@register(\"demand\", \"my_demand\")\nclass MyDemandGenerator(DemandGenerator):\n    def generate(self, T: int, seed: int | None = None) -\u003e np.ndarray:\n        ...\n```\n\n### PR Checklist\n\n- [ ] Component file in the appropriate module (`forecast/`, `policy/`, `demand/`)\n- [ ] `@register()` decorator with unique name\n- [ ] Updated module `__init__.py`\n- [ ] Unit test in `tests/`\n- [ ] Benchmark output from `python benchmarks/run_leaderboard.py` pasted in PR description\n- [ ] Optional dependencies added to `pyproject.toml` extras (if any)\n- [ ] If your component requires heavy/optional dependencies (torch, gluonts, etc.),\n  guard tests with `pytest.mark.skipif` so they skip gracefully when deps are missing\n\n## Default Supply Chain Configuration\n\n| Echelon | Role | Lead Time | h (holding) | b (backorder) |\n|---------|------|-----------|-------------|---------------|\n| E1 | Distributor / OEM | 2 weeks | 0.15 | 0.60 |\n| E2 | Assembly \u0026 Test (OSAT) | 4 weeks | 0.12 | 0.50 |\n| E3 | Foundry / Fab | 12 weeks | 0.08 | 0.40 |\n| E4 | Wafer / Material Supplier | 8 weeks | 0.05 | 0.30 |\n\n## Vectorized Monte Carlo Simulation\n\nFor large-scale experiments, use the matrix-based engine that processes\nN demand paths simultaneously via NumPy broadcasting:\n\n```python\nfrom deepbullwhip import SemiconductorDemandGenerator, VectorizedSupplyChain\n\ngen = SemiconductorDemandGenerator()\ndemand = gen.generate_batch(T=156, n_paths=1000, seed=42)  # (1000, 156)\n\nvchain = VectorizedSupplyChain()\nfm = np.full_like(demand, demand.mean())\nfs = np.full_like(demand, demand.std())\nresult = vchain.simulate(demand, fm, fs)\n\n# Average metrics across all 1000 paths\nprint(result.mean_metrics())\n\n# Extract a single path as standard SimulationResult\nsr = result.to_simulation_result(path_index=0)\n```\n\n**Benchmark (N=1000, T=156, K=4):**\n\n| Engine | Time | Speedup |\n|--------|------|---------|\n| Serial (`SerialSupplyChain`) | 3.9s | 1x |\n| Vectorized (`VectorizedSupplyChain`) | 0.04s | **~100x** |\n\nThe vectorized engine uses:\n- Pre-allocated `(N, K, T)` order/inventory/cost matrices\n- Circular buffer pipeline with O(1) indexing (vs O(L) list.pop)\n- Fully vectorized OUT policy and newsvendor cost across N paths and K echelons per time step\n- Batch demand generation via `generate_batch()` with `(N, T)` noise matrix\n\n## Customization\n\n### Custom echelon configuration\n\n```python\nfrom deepbullwhip import EchelonConfig, SerialSupplyChain\n\nconfigs = [\n    EchelonConfig(\"Retailer\", lead_time=1, holding_cost=0.20, backorder_cost=0.80),\n    EchelonConfig(\"Manufacturer\", lead_time=6, holding_cost=0.10, backorder_cost=0.40),\n]\nchain = SerialSupplyChain.from_config(configs)\n```\n\n### Custom ordering policy\n\n```python\nfrom deepbullwhip.policy.base import OrderingPolicy\n\nclass MyPolicy(OrderingPolicy):\n    def compute_order(self, inventory_position, forecast_mean, forecast_std):\n        # Your logic here\n        return max(0.0, forecast_mean - inventory_position)\n```\n\n### Custom cost function\n\n```python\nfrom deepbullwhip.cost.base import CostFunction\n\nclass MyCost(CostFunction):\n    def compute(self, inventory):\n        # Your logic here\n        return abs(inventory) * 0.1\n```\n\n## Visualization\n\n### Diagnostic plots\n\nAll plot functions return `matplotlib.figure.Figure` objects and support\n`width=\"single\"` (3.5\") or `width=\"double\"` (7.0\") for journal formatting.\nColors use the KFUPM AI V\u0026V Lab palette.\n\n```python\nfrom deepbullwhip.diagnostics.plots import (\n    plot_demand_trajectory,\n    plot_order_quantities,\n    plot_inventory_levels,\n    plot_inventory_position,\n    plot_order_streams,\n    plot_cost_timeseries,\n    plot_cost_decomposition,\n    plot_bullwhip_amplification,\n    plot_summary_dashboard,\n    plot_echelon_detail,\n)\n\nfig = plot_summary_dashboard(demand, result)\nfig.savefig(\"dashboard.pdf\", dpi=300)\n```\n\n### Network and geographic visualization\n\n```python\nfrom deepbullwhip.diagnostics.network import (\n    kfupm_petrochemical_network,\n    plot_network_diagram,\n    plot_supply_chain_map,\n)\n\nnetwork = kfupm_petrochemical_network()\nfig = plot_network_diagram(network, sim_result=result)\nfig = plot_supply_chain_map(network, sim_result=result)\n```\n\n### Batch figure generation\n\n```bash\npython scripts/visualize.py --save --outdir figures --dpi 600\n```\n\n## Project Structure\n\n```\ndeepbullwhip/\n├── __init__.py                 # Public API re-exports\n├── _types.py                   # TimeSeries, EchelonResult, SimulationResult\n├── registry.py                 # Decorator-based @register system\n├── sensitivity.py              # Forecast sensitivity (lambda_f)\n├── demand/\n│   ├── base.py                 # DemandGenerator ABC\n│   ├── semiconductor.py        # AR(1) + seasonal + shock\n│   ├── beer_game.py            # Classic Beer Game step demand\n│   ├── arma.py                 # General ARMA(p,q) process\n│   └── replay.py              # Replay from historical data\n├── policy/\n│   ├── base.py                 # OrderingPolicy ABC\n│   ├── order_up_to.py          # Order-Up-To (OUT) policy\n│   ├── proportional_out.py     # Proportional OUT (POUT)\n│   ├── constant_order.py       # Constant order (BWR=0)\n│   └── smoothing_out.py        # Smoothing OUT\n├── cost/\n│   ├── base.py                 # CostFunction ABC\n│   ├── newsvendor.py           # Newsvendor h/b cost\n│   └── perishable.py           # Perishable (h+b+obsolescence)\n├── forecast/\n│   ├── base.py                 # Forecaster ABC\n│   ├── naive.py                # Naive (sample mean/std)\n│   ├── moving_average.py       # Rolling window MA\n│   ├── exponential_smoothing.py # Single exponential smoothing\n│   └── deepar.py               # DeepAR neural forecaster (GluonTS)\n├── metrics/\n│   ├── bullwhip.py             # BWR, CumulativeBWR\n│   ├── inventory.py            # NSAmp, FillRate\n│   ├── cost.py                 # TotalCost\n│   └── bounds.py               # ChenLowerBound\n├── benchmark/\n│   ├── runner.py               # BenchmarkRunner\n│   ├── configs.py              # Predefined chain configs\n│   └── report.py               # LaTeX, Markdown, CSV export\n├── datasets/\n│   ├── beer_game.py            # Beer Game step demand\n│   ├── synthetic.py            # AR(1), ARMA generators\n│   ├── m5.py                   # M5 Walmart data loader\n│   └── wsts.py                 # WSTS semiconductor data\n├── chain/\n│   ├── config.py               # EchelonConfig + defaults\n│   ├── echelon.py              # SupplyChainEchelon\n│   ├── serial.py               # SerialSupplyChain\n│   ├── vectorized.py           # VectorizedSupplyChain (N,K,T) matrix engine\n│   ├── graph.py                # SupplyChainGraph, EdgeConfig (v0.3.0)\n│   └── network_sim.py          # NetworkSupplyChain (v0.3.0)\n├── network/                    # NetworkX integration (v0.3.0)\n│   ├── convert.py              # to_networkx, from_networkx\n│   └── analysis.py             # critical path, centrality, etc.\n├── optimize/                   # Pyomo optimization (v0.3.0)\n│   ├── inventory.py            # Multi-echelon inventory optimization\n│   ├── policy_tuning.py        # Service level / smoothing tuning\n│   └── network_design.py       # Facility location MIP (experimental)\n└── diagnostics/\n    ├── metrics.py              # Bullwhip ratio, fill rate, etc.\n    ├── plots.py                # 10 publication-grade plot functions\n    ├── network.py              # Network diagram + geographic map\n    └── graphviz_viz.py         # Graphviz rendering (v0.3.0)\n\n├── schema/                     # JSON schema (v0.3.0)\n│   ├── definition.py           # NodeLayoutHint, LayoutDefaults, NetworkMetadata\n│   └── io.py                   # to_json, from_json, save/load\n├── render/                     # Multi-backend renderer (v0.3.0)\n│   ├── theme.py                # 4 built-in themes + registry\n│   ├── layout.py               # Auto-layout from topology\n│   ├── _matplotlib.py          # Matplotlib backend\n│   ├── _graphviz.py            # Graphviz backend\n│   ├── _tikz.py                # TikZ/LaTeX backend\n│   └── api.py                  # Unified render_graph() entry point\n\nbenchmarks/\n└── run_leaderboard.py              # Auto-generate docs/LEADERBOARD.md\n\nscripts/\n├── visualize.py                    # Batch diagnostic figure generation\n├── generate_caie_figures.py        # CAIE 2026 paper figures (14 figures)\n├── run_all_experiments.py           # CAIE 2026 paper numerical results\n├── exp_chen_validation.py          # Chen et al. (2000) BWR formula validation\n├── exp_corollary1.py               # Cumulative BWR concentration theorem\n├── exp_cost_policy.py              # Cost asymmetry x policy interaction\n└── exp_pareto.py                   # POUT alpha Pareto frontier\n\ntests/                          # 385 unit tests, 98%+ coverage\nnotebooks/                      # All notebooks run on Google Colab\n├── 01_supply_chain_cost.ipynb      # Costs, inventory, service level tradeoffs\n├── 02_bullwhip_effect.ipynb        # Bullwhip amplification \u0026 Monte Carlo\n├── 03_custom_policies.ipynb        # Custom policies, smoothing, @register\n├── 04_network_viz_tutorial.ipynb   # DAG topologies, JSON schema, themes, NetworkX\n├── 05_pyomo_optimization.ipynb     # Policy tuning, inventory opt, network design\n├── 06_benchmark_policies.ipynb      # Systematic policy comparison\n├── 07_benchmark_forecasters.ipynb   # Forecaster comparison\n└── 08_benchmark_real_datasets.ipynb # M5, WSTS, Beer Game benchmarks\n```\n\n## Testing\n\n```bash\n# Run all tests\npython -m pytest tests/ -v\n\n# With coverage\npython -m pytest tests/ --cov=deepbullwhip --cov-report=term-missing\n```\n\nCurrent: **385 tests** across all modules, **98%+ coverage**.\n\n## Tutorials\n\nAll notebooks include Google Colab setup cells and run standalone.\n\n| Notebook | Topic |\n|----------|-------|\n| [01 Supply Chain Cost](notebooks/01_supply_chain_cost.ipynb) | Newsvendor costs, holding vs backorder, service level tradeoffs |\n| [02 Bullwhip Effect](notebooks/02_bullwhip_effect.ipynb) | Bullwhip amplification, Monte Carlo validation, Chen lower bound |\n| [03 Custom Policies](notebooks/03_custom_policies.ipynb) | Implementing \u0026 registering custom ordering policies |\n| [04 Network \u0026 Viz](notebooks/04_network_viz_tutorial.ipynb) | DAG topologies, JSON schema, NetworkX integration, multi-backend rendering |\n| [05 Pyomo Optimization](notebooks/05_pyomo_optimization.ipynb) | Service level tuning, inventory optimization, network design |\n\n## Citation\n\nIf you use DeepBullwhip in your research, please cite:\n\n```bibtex\n@software{deepbullwhip,\n  title  = {DeepBullwhip: Multi-Tier Supply Chain Bullwhip Effect Simulator},\n  author = {Arief, Mansur M.},\n  url    = {https://github.com/ai-vnv/deepbullwhip},\n  year   = {2025}\n}\n```\n\n## Documentation\n\nFull API documentation is available at [ai-vnv.github.io/deepbullwhip](https://ai-vnv.github.io/deepbullwhip).\n\n## License\n\nMIT License. See [LICENSE](LICENSE) for details.\n\nDeveloped and maintained by the [AI V\u0026V Lab](https://ai-vnv.kfupm.io) at KFUPM.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fai-vnv%2Fdeepbullwhip","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fai-vnv%2Fdeepbullwhip","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fai-vnv%2Fdeepbullwhip/lists"}