├── .gitignore ├── README.md ├── __init__.py ├── des_simulation_api.py ├── des_simulation_opt_api.py ├── des_simulation_streamlit.py ├── discrete_event_simulation ├── __init__.py ├── plots │ ├── balance.png │ ├── holding.png │ └── inventory.png └── single │ ├── __init__.py │ ├── des_random_optimzer.py │ ├── des_single_continous.py │ └── simulation_classes.py ├── gen_algo_simulation ├── __init__.py ├── plotter.py ├── simulation_settings.py └── single │ ├── Demand.py │ ├── Leadtime.py │ ├── __init__.py │ ├── api │ └── sim_api.py │ ├── continous │ ├── __init__.py │ ├── optimizer.py │ ├── setup_simulation.py │ └── simulation.py │ ├── plots │ └── inventory.png │ └── streamlit │ └── app.py ├── gen_simulation_streamlit.py ├── gen_single_item_api.py ├── gen_single_item_opt_api.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | myenv/ 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Inventory Optimization with DES Simulation and Genetic Algorithms 2 | This repository offers a comprehensive tool designed for inventory scientists and developers to enhance inventory management through advanced simulation and optimization techniques. By integrating Discrete Event Simulation (DES) with Genetic Algorithms (GA), this project provides a robust framework to model, analyze, and optimize inventory systems, facilitating informed decision-making regarding stock levels, replenishment strategies, and resource allocation. 3 | 4 | Key Features 5 | Inventory Simulation: Utilizes DES to create realistic models of inventory processes, capturing complexities such as demand variability, lead times, and restocking policies. 6 | 7 | Optimization Techniques: Employs Genetic Algorithms to determine optimal inventory parameters, including reorder points and order quantities, aiming to minimize costs and prevent stockouts. 8 | 9 | Performance Optimization: Incorporates Numba, a just-in-time compiler for Python, to accelerate computational tasks, ensuring efficient handling of large-scale simulations. 10 | 11 | Interactive User Interface: Features a web-based interface built with Streamlit, allowing users to input parameters, run simulations, and visualize outcomes seamlessly. 12 | 13 | API Integration: Provides a FastAPI backend to serve simulation results, enabling easy integration with other systems and facilitating automated workflows. 14 | 15 | Repository Structure 16 | Simulation Modules: 17 | 18 | discrete_event_simulation/: Contains scripts and modules for setting up and running DES models of inventory systems. 19 | gen_algo_simulation/: Houses components related to the implementation of Genetic Algorithms for optimizing inventory parameters. 20 | API Services: 21 | 22 | des_simulation_api.py: Defines FastAPI endpoints to execute DES simulations and retrieve results programmatically. 23 | des_simulation_opt_api.py: Offers API services focused on optimization tasks using DES models. 24 | gen_single_item_api.py: Provides API endpoints for single-item inventory optimization using Genetic Algorithms. 25 | gen_single_item_opt_api.py: Extends the API services to handle optimization scenarios for single-item inventories. 26 | User Interfaces: 27 | 28 | des_simulation_streamlit.py: Streamlit-based application for interactive DES simulation, allowing users to modify parameters and visualize results in real-time. 29 | gen_simulation_streamlit.py: Streamlit interface dedicated to Genetic Algorithm simulations, facilitating user-friendly optimization processes. 30 | Configuration and Dependencies: 31 | 32 | requirements.txt: Lists all necessary Python packages and dependencies required to set up and run the project seamlessly. 33 | .gitignore: Specifies files and directories to be excluded from version control, maintaining a clean repository. 34 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/__init__.py -------------------------------------------------------------------------------- /des_simulation_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel, Field 3 | import numpy as np 4 | 5 | from discrete_event_simulation.single.des_single_continous import * 6 | 7 | # Define the app 8 | app = FastAPI() 9 | 10 | # Define the request body model 11 | class SimulationConfig(BaseModel): 12 | 13 | reorder_level:float = Field(..., example=10) 14 | reorder_qty:float = Field(..., example=20) 15 | purchase_cost:float = Field(..., example=1000) 16 | selling_price:float = Field(..., example=2000) 17 | 18 | holding_cost_unit : float = Field(..., example=2) 19 | ordering_cost : float = Field(..., example=1) 20 | other_costs : float = Field(..., example=4) 21 | lead_time_mean : float = Field(..., example=53) 22 | lead_time_std : float = Field(..., example=20) 23 | delivery_batches : float = Field(..., example=1) 24 | daily_demand_mean : float = Field(..., example=0.0813) 25 | daily_demand_std : float = Field(..., example=12) 26 | review_period : float = Field(..., example=1) 27 | backlog_cost_unit : float = Field(..., example=3) 28 | safety_stock : float = Field(..., example=0) 29 | balance : float = Field(..., example=0) 30 | TIME : int = Field(..., example=1000) 31 | 32 | 33 | 34 | 35 | class SimulationResult(BaseModel): 36 | cost_of_inventory : float 37 | cycle_service_level : float 38 | fill_rate : float 39 | total_profit : float 40 | 41 | 42 | 43 | @app.get("/") 44 | async def root(): 45 | return {"message": "Welcome to the Fast Inventory Optimizer API!"} 46 | 47 | 48 | @app.post("/run_simulation", response_model=SimulationResult) 49 | async def run_simulation(config: SimulationConfig): 50 | 51 | reorder_level = config.reorder_level 52 | reorder_qty = config.reorder_qty 53 | purchase_cost = config.purchase_cost 54 | selling_price = config.selling_price 55 | 56 | holding_cost_unit = config.holding_cost_unit 57 | ordering_cost = config.ordering_cost 58 | other_costs = config.other_costs 59 | lead_time_mean = config.lead_time_mean 60 | lead_time_std = config.lead_time_std 61 | delivery_batches = config.delivery_batches 62 | daily_demand_mean = config.daily_demand_mean 63 | daily_demand_std = config.daily_demand_std 64 | review_period = config.review_period 65 | backlog_cost_unit= config.backlog_cost_unit 66 | safety_stock = config.safety_stock 67 | balance = config.balance 68 | TIME = config.TIME 69 | 70 | cost_of_inventory, cycle_service_level, fill_rate, total_profit = run_simulation_reorder(reorder_level, reorder_qty, purchase_cost, selling_price, holding_cost_unit, ordering_cost, other_costs, lead_time_mean, lead_time_std, delivery_batches, daily_demand_mean, daily_demand_std, review_period, backlog_cost_unit, safety_stock, balance, TIME) 71 | # Run the simulation 72 | 73 | # Prepare the response 74 | response = SimulationResult( 75 | cost_of_inventory=cost_of_inventory, 76 | cycle_service_level=cycle_service_level, 77 | fill_rate=fill_rate, 78 | total_profit=total_profit 79 | ) 80 | 81 | # Return the response as JSON 82 | return response 83 | -------------------------------------------------------------------------------- /des_simulation_opt_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel, Field 3 | import numpy as np 4 | from discrete_event_simulation.single.simulation_classes import * 5 | from discrete_event_simulation.single.des_single_continous import * 6 | from discrete_event_simulation.single.des_random_optimzer import * 7 | # Define the app 8 | app = FastAPI() 9 | 10 | # Define the request body model 11 | class SimulationConfig(BaseModel): 12 | 13 | reorder_level:float = Field(..., example=10) 14 | reorder_qty:float = Field(..., example=20) 15 | purchase_cost:float = Field(..., example=1000) 16 | selling_price:float = Field(..., example=2000) 17 | 18 | holding_cost_unit : float = Field(..., example=2) 19 | ordering_cost : float = Field(..., example=1) 20 | other_costs : float = Field(..., example=4) 21 | lead_time_mean : float = Field(..., example=53) 22 | lead_time_std : float = Field(..., example=20) 23 | delivery_batches : float = Field(..., example=1) 24 | daily_demand_mean : float = Field(..., example=0.0813) 25 | daily_demand_std : float = Field(..., example=12) 26 | review_period : float = Field(..., example=1) 27 | backlog_cost_unit : float = Field(..., example=3) 28 | safety_stock : float = Field(..., example=0) 29 | balance : float = Field(..., example=0) 30 | TIME : int = Field(..., example=1000) 31 | 32 | 33 | 34 | 35 | 36 | class SimulationResult(BaseModel): 37 | ROP : float 38 | EOQ : float 39 | TotalCost : float 40 | 41 | 42 | @app.get("/") 43 | async def root(): 44 | return {"message": "Welcome to the Fast Inventory Optimizer API!"} 45 | 46 | 47 | @app.post("/run_simulation", response_model=SimulationResult) 48 | async def run_simulation(config: SimulationConfig): 49 | 50 | reorder_level = config.reorder_level 51 | reorder_qty = config.reorder_qty 52 | purchase_cost = config.purchase_cost 53 | selling_price = config.selling_price 54 | 55 | holding_cost_unit = config.holding_cost_unit 56 | ordering_cost = config.ordering_cost 57 | other_costs = config.other_costs 58 | lead_time_mean = config.lead_time_mean 59 | lead_time_std = config.lead_time_std 60 | delivery_batches = config.delivery_batches 61 | daily_demand_mean = config.daily_demand_mean 62 | daily_demand_std : float = Field(..., example=12) 63 | review_period : float = Field(..., example=1) 64 | backlog_cost_unit : float = Field(..., example=3) 65 | safety_stock : float = Field(..., example=0) 66 | balance : float = Field(..., example=0) 67 | TIME : int = Field(..., example=1000) 68 | 69 | Setting = Setting(reorder_level,reorder_qty,purchase_cost,selling_price,holding_cost_unit,ordering_cost,other_costs,lead_time_mean,lead_time_std,delivery_batches,daily_demand_mean,daily_demand_std,review_period,backlog_cost_unit,safety_stock,balance,TIME) 70 | 71 | best_ROP, best_EOQ, best_cost = simulated_annealing_optimizer(1000, Setting) 72 | # Run the simulation 73 | 74 | # Prepare the response 75 | response = SimulationResult( 76 | ROP = best_ROP, 77 | EOQ = best_EOQ, 78 | TotalCost = best_cost 79 | ) 80 | 81 | # Return the response as JSON 82 | return response 83 | -------------------------------------------------------------------------------- /des_simulation_streamlit.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from pydantic import BaseModel, Field 3 | import numpy as np 4 | from discrete_event_simulation.single.des_single_continous import * 5 | 6 | class SimulationConfig(BaseModel): 7 | reorder_level:float = Field(..., example=10) 8 | reorder_qty:float = Field(..., example=20) 9 | purchase_cost:float = Field(..., example=1000) 10 | selling_price:float = Field(..., example=2000) 11 | 12 | holding_cost_unit : float = Field(..., example=2) 13 | ordering_cost : float = Field(..., example=1) 14 | other_costs : float = Field(..., example=4) 15 | lead_time_mean : float = Field(..., example=53) 16 | lead_time_std : float = Field(..., example=20) 17 | delivery_batches : float = Field(..., example=1) 18 | daily_demand_mean : float = Field(..., example=0.0813) 19 | daily_demand_std : float = Field(..., example=12) 20 | review_period : float = Field(..., example=1) 21 | backlog_cost_unit : float = Field(..., example=3) 22 | safety_stock : float = Field(..., example=0) 23 | balance : float = Field(..., example=0) 24 | TIME : int = Field(..., example=1000) 25 | 26 | class SimulationResult(BaseModel): 27 | cost_of_inventory : float 28 | cycle_service_level : float 29 | fill_rate : float 30 | total_profit : float 31 | 32 | st.title("Welcome to the Inventory Optimizer App!") 33 | 34 | reorder_level = st.number_input("Enter Reorder Level", value=10.0) 35 | reorder_qty = st.number_input("Enter Reorder Quantity", value=20.0) 36 | purchase_cost = st.number_input("Enter Purchase Cost", value=1000.0) 37 | selling_price = st.number_input("Enter Selling Price", value=2000.0) 38 | holding_cost_unit = st.number_input("Enter Holding Cost per Unit", value=2.0) 39 | ordering_cost = st.number_input("Enter Ordering Cost", value=1.0) 40 | other_costs = st.number_input("Enter Other Costs", value=4.0) 41 | lead_time_mean = st.number_input("Enter Lead Time Mean", value=53.0) 42 | lead_time_std = st.number_input("Enter Lead Time Standard Deviation", value=20.0) 43 | delivery_batches = st.number_input("Enter Delivery Batches", value=1.0) 44 | daily_demand_mean = st.number_input("Enter Daily Demand Mean", value=0.0813) 45 | daily_demand_std = st.number_input("Enter Daily Demand Standard Deviation", value=12.0) 46 | review_period = st.number_input("Enter Review Period", value=1.0) 47 | backlog_cost_unit = st.number_input("Enter Backlog Cost per Unit", value=3.0) 48 | safety_stock = st.number_input("Enter Safety Stock", value=0.0) 49 | balance = st.number_input("Enter Balance", value=0.0) 50 | TIME = st.number_input("Enter Time", value=1000, format="%d") 51 | 52 | if st.button("Run Simulation"): 53 | config = SimulationConfig( 54 | reorder_level=reorder_level, 55 | reorder_qty=reorder_qty, 56 | purchase_cost=purchase_cost, 57 | selling_price=selling_price, 58 | holding_cost_unit=holding_cost_unit, 59 | ordering_cost=ordering_cost, 60 | other_costs=other_costs, 61 | lead_time_mean=lead_time_mean, 62 | lead_time_std=lead_time_std, 63 | delivery_batches=delivery_batches, 64 | daily_demand_mean=daily_demand_mean, 65 | daily_demand_std=daily_demand_std, 66 | review_period=review_period, 67 | backlog_cost_unit=backlog_cost_unit, 68 | safety_stock=safety_stock, 69 | balance=balance, 70 | TIME=TIME 71 | ) 72 | 73 | cost_of_inventory, cycle_service_level, fill_rate, total_profit,plots = run_simulation_reorder( 74 | config.reorder_level, 75 | config.reorder_qty, 76 | config.purchase_cost, 77 | config.selling_price, 78 | config.holding_cost_unit, 79 | config.ordering_cost, 80 | config.other_costs, 81 | config.lead_time_mean, 82 | config.lead_time_std, 83 | config.delivery_batches, 84 | config.daily_demand_mean, 85 | config.daily_demand_std, 86 | config.review_period, 87 | config.backlog_cost_unit, 88 | config.safety_stock, 89 | config.balance, 90 | config.TIME, 91 | True 92 | ) 93 | 94 | 95 | 96 | 97 | 98 | 99 | # Prepare the response 100 | result = SimulationResult( 101 | cost_of_inventory=cost_of_inventory, 102 | cycle_service_level=cycle_service_level, 103 | fill_rate=fill_rate, 104 | total_profit=total_profit 105 | ) 106 | 107 | st.json(result.dict()) # Display the result as JSON 108 | st.pyplot(plots) -------------------------------------------------------------------------------- /discrete_event_simulation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/discrete_event_simulation/__init__.py -------------------------------------------------------------------------------- /discrete_event_simulation/plots/balance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/discrete_event_simulation/plots/balance.png -------------------------------------------------------------------------------- /discrete_event_simulation/plots/holding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/discrete_event_simulation/plots/holding.png -------------------------------------------------------------------------------- /discrete_event_simulation/plots/inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/discrete_event_simulation/plots/inventory.png -------------------------------------------------------------------------------- /discrete_event_simulation/single/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/discrete_event_simulation/single/__init__.py -------------------------------------------------------------------------------- /discrete_event_simulation/single/des_random_optimzer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import simpy 3 | 4 | 5 | from discrete_event_simulation.single.simulation_classes import * 6 | from typing import List, Tuple, Callable 7 | import random 8 | from discrete_event_simulation.single.des_single_continous import * 9 | 10 | import math 11 | 12 | def cost_function(ROP:float, EOQ:float,Setting:object) -> float: 13 | 14 | simulation_time = Setting.TIME 15 | purchase_cost = Setting.purchase_cost 16 | selling_price = Setting.selling_price 17 | 18 | holding_cost_unit = Setting.holding_cost_unit 19 | ordering_cost_unit = Setting.ordering_cost 20 | other_costs = Setting.other_costs 21 | lead_time_mean = Setting.lead_time_mean 22 | lead_time_std = Setting.lead_time_std 23 | delivery_batches = Setting.delivery_batches 24 | daily_demand_mean =Setting.daily_demand_mean 25 | daily_demand_std =Setting.daily_demand_std 26 | review_period = Setting.review_period 27 | backlog_cost_unit = Setting.backlog_cost_unit 28 | safety_stock = Setting.safety_stock 29 | balance = Setting.balance 30 | constants = Constants(ROP, EOQ, purchase_cost, selling_price, holding_cost_unit, ordering_cost_unit, other_costs, lead_time_mean, lead_time_std, delivery_batches, daily_demand_mean, daily_demand_std, review_period, backlog_cost_unit, safety_stock, balance) 31 | variables = Variables(constants) 32 | functions = ExternalFunctions() 33 | env = simpy.Environment() 34 | simulation = Inventory_Simulation(env, constants, variables, functions) 35 | env.process(simulation.runner_setup()) 36 | env.process(simulation.observe()) 37 | env.run(until=simulation_time) 38 | return simulation.calculate_total_cost() 39 | 40 | def simulated_annealing_optimizer(n_iterations:int, Setting:object, initial_temp:float=1000, cool_down_rate:float=0.9) -> Tuple[float, float, float]: 41 | 42 | # Initialize random solution (ROP and EOQ) within a given range 43 | current_ROP = random.randint(4, 100) 44 | current_EOQ = random.randint(2, 100) 45 | current_cost = cost_function(current_ROP, current_EOQ, Setting) 46 | 47 | best_ROP = current_ROP 48 | best_EOQ = current_EOQ 49 | best_cost = current_cost 50 | 51 | # Start with a high initial temperature (initial_temp) and decrease it over time (cool_down_rate) 52 | temp = initial_temp 53 | 54 | for _ in range(n_iterations): 55 | # Create a new candidate solution 56 | new_ROP = random.randint(4, 100) 57 | new_EOQ = random.randint(2, 100) 58 | new_cost = cost_function(new_ROP, new_EOQ, Setting) 59 | 60 | # Check if new solution is better, if not accept it probabilistically based on temperature 61 | if new_cost < current_cost or random.uniform(0, 1) < math.exp((current_cost - new_cost) / temp): 62 | current_ROP, current_EOQ, current_cost = new_ROP, new_EOQ, new_cost 63 | 64 | # Keep track of the best solution found 65 | if current_cost < best_cost: 66 | best_ROP, best_EOQ, best_cost = current_ROP, current_EOQ, current_cost 67 | 68 | # Reduce the temperature (Cooling down) 69 | temp *= cool_down_rate 70 | 71 | return best_ROP, best_EOQ, best_cost 72 | 73 | # Run the Simulated Annealing optimizer 74 | best_ROP, best_EOQ, best_cost = simulated_annealing_optimizer(1000, Setting) 75 | print(f'Best ROP: {best_ROP}') 76 | print(f'Best EOQ: {best_EOQ}') 77 | print(f'Best cost: {best_cost}') -------------------------------------------------------------------------------- /discrete_event_simulation/single/des_single_continous.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import simpy 3 | import matplotlib.pyplot as plt 4 | np.random.seed(0) 5 | from math import ceil 6 | 7 | from typing import List, Tuple, Callable 8 | from discrete_event_simulation.single.simulation_classes import * 9 | 10 | 11 | SAVE_PLOTS_FOLDER = "/mnt/d/Portfolio/Inventory_Optimization/inventory_optimizer_app/discrete_event_simulation/plots" 12 | class Inventory_Simulation: 13 | 14 | def __init__(self, env:simpy.Environment, constants: Constants, variables: Variables, functions: ExternalFunctions): 15 | self.constants = constants 16 | self.variables = variables 17 | self.functions = functions 18 | self.env = env 19 | def generate_interarrival(self) -> float: 20 | return self.functions.generate_interarrival(self.constants.customer_arrival) 21 | 22 | def generate_leadtime(self) -> float: 23 | __leadtime = self.functions.generate_leadtime(self.constants.lead_time_mean, self.constants.lead_time_std, 1).item() 24 | return(__leadtime if __leadtime>=0 else self.constants.lead_time_mean) 25 | def customer_generate_demand(self) -> float: 26 | return self.functions.generate_demand(self.constants.customer_purchase_mean, self.constants.customer_purchase_std, 1).item() 27 | 28 | def generate_demand(self) -> float: 29 | __temp = self.functions.generate_demand(self.constants.daily_demand_mean, self.constants.daily_demand_std, 1).item() 30 | return(__temp if __temp>=0 else 0) 31 | 32 | def total_cost(self): 33 | """ 34 | Calculate the total ordering cost. 35 | """ 36 | return self.variables.holding_cost+ self.variables.num_orders_placed * self.constants.ordering_cost 37 | 38 | def calculate_profit(self) -> float: 39 | """ 40 | Calculate the total profit. 41 | """ 42 | total_revenue = self.constants.selling_price * sum(self.variables.demands) 43 | total_unmet_demand_cost = self.constants.selling_price * self.variables.service_outage_cnt 44 | total_cost = self.calculate_total_cost() + total_unmet_demand_cost 45 | tax_rate = 0.2 46 | tax = tax_rate * total_revenue 47 | 48 | profit = total_revenue - total_cost - tax 49 | return profit 50 | 51 | 52 | def handle_order(self): 53 | # adjust reorder amount to cover negative inventory (backlogged orders) 54 | required_inventory = max(self.constants.reorder_level + 1, -self.variables.inventory) 55 | 56 | self.variables.num_ordered = required_inventory 57 | self.variables.num_ordered = ceil(self.variables.num_ordered/self.constants.reorder_qty)*self.constants.reorder_qty 58 | 59 | self.variables.balance = self.constants.purchase_cost*self.variables.num_ordered + self.constants.ordering_cost 60 | self.variables.num_orders_placed += 1 61 | for _ in range(int(1/self.constants.delivery_batches)): 62 | yield self.env.timeout(self.generate_leadtime()) 63 | self.variables.inventory += self.variables.num_ordered*self.constants.delivery_batches 64 | self.variables.num_ordered = 0 65 | 66 | 67 | def observe(self): 68 | while True: 69 | self.variables.obs_time.append(self.env.now) 70 | self.variables.inventory_level.append(self.variables.inventory) 71 | if self.variables.demand > self.variables.inventory: 72 | self.variables.service_outage_cnt += 1 73 | self.variables.costslevel.append(self.variables.balance) 74 | yield self.env.timeout(1) 75 | def runner_setup(self): 76 | while True: 77 | interarrival = 1 78 | yield self.env.timeout(interarrival) 79 | self.variables.holding_cost.append(max(self.variables.inventory * self.constants.holding_cost_unit, 0)) # holding cost only applies to positive inventory 80 | 81 | self.variables.demand = self.generate_demand() 82 | self.variables.demands.append(self.variables.demand) 83 | self.variables.balance += self.constants.selling_price * min(self.variables.demand, self.variables.inventory) 84 | self.variables.inventory -= self.variables.demand 85 | 86 | #if self.variables.demand < self.variables.inventory: 87 | # self.variables.balance += self.constants.selling_price*self.variables.demand 88 | # self.variables.inventory -= self.variables.demand 89 | #else: 90 | # self.variables.balance += self.constants.selling_price*self.variables.inventory 91 | # self.variables.inventory = 0 92 | if self.variables.inventory <= (self.constants.reorder_level + self.variables.safety_stock) and self.variables.num_ordered ==0: 93 | self.env.process(self.handle_order()) 94 | 95 | def plot(self, plot_type): 96 | plt.figure() 97 | if plot_type == 'inventory': 98 | plt.step(self.variables.obs_time, self.variables.inventory_level) 99 | plt.ylabel('SKU level') 100 | elif plot_type == 'balance': 101 | plt.step(self.variables.obs_time, self.variables.costslevel) 102 | plt.ylabel('SKU balance USD') 103 | elif plot_type == 'sales': 104 | plt.step(self.variables.obs_time, self.variables.costslevel) 105 | plt.ylabel('SKU balance USD') 106 | elif plot_type == 'holding': 107 | plt.step(np.arange(len(self.variables.holding_cost)), self.variables.holding_cost) 108 | plt.ylabel('holding costs') 109 | elif plot_type == 'demand': 110 | plt.step(np.arange(len(self.variables.demands)), self.variables.demands) 111 | plt.ylabel('Demands') 112 | else: 113 | raise ValueError(f'Invalid plot_type: {plot_type}') 114 | plt.xlabel('Time') 115 | plt.savefig(f'{SAVE_PLOTS_FOLDER}/{plot_type}.png') 116 | return plt 117 | 118 | def plot_all_inventory(self): 119 | 120 | fig, axs = plt.subplots(3, figsize=(12, 15)) 121 | 122 | 123 | #plot inventory level 124 | axs[0].step(self.variables.obs_time, self.variables.inventory_level) 125 | axs[0].set_ylabel('SKU level') 126 | axs[0].set_xlabel('Time') 127 | axs[0].set_title('Inventory Level') 128 | 129 | #plot balance 130 | axs[1].step(self.variables.obs_time, self.variables.costslevel) 131 | axs[1].set_ylabel('SKU balance USD') 132 | axs[1].set_xlabel('Time') 133 | axs[1].set_title('Balance') 134 | 135 | #plot holding cost 136 | axs[2].step(np.arange(len(self.variables.holding_cost)), self.variables.holding_cost) 137 | axs[2].set_ylabel('holding costs') 138 | axs[2].set_xlabel('Time') 139 | axs[2].set_title('Holding Cost') 140 | fig.tight_layout() 141 | 142 | return fig 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | def service_level(self): 153 | __temp_level= np.array(self.variables.inventory_level) 154 | __temp_level1 = __temp_level[__temp_level==0] 155 | if len(__temp_level1)==0: 156 | return 1 157 | else: 158 | return (1 - len(__temp_level1)/len(__temp_level)) 159 | 160 | def avg_cost_of_inventory(self): 161 | #calculate the average cost of inventory holding 162 | return sum(self.variables.holding_cost)/len(self.variables.holding_cost) 163 | 164 | def print_service_level(self): 165 | return 1-self.variables.service_outage_cnt 166 | 167 | def total_holding_cost(self): 168 | """ 169 | Calculate the total holding cost. 170 | """ 171 | return sum(self.variables.holding_cost) 172 | 173 | def calculate_fill_rate(self): 174 | """ 175 | Calculate the fill rate. 176 | """ 177 | total_demand = sum(self.variables.demands) 178 | unmet_demand = self.variables.service_outage_cnt 179 | if total_demand == 0: 180 | return 1 181 | return (total_demand - unmet_demand) / total_demand 182 | 183 | def calculate_cycle_rate(self): 184 | # calculate the cycle service level 185 | stockout_cycles = [1 for level in self.variables.inventory_level if level < 0] 186 | return 1 - sum(stockout_cycles) / len(self.variables.inventory_level) 187 | 188 | def service_level_alpha(self): 189 | stockout_cycles = [1 for level in self.variables.inventory_level if level < 0] 190 | return 1 - sum(stockout_cycles) / len(self.variables.inventory_level) 191 | 192 | def calculate_fill_rate(self): 193 | stockout_periods = [1 for demand, level in zip(self.variables.demands, self.variables.inventory_level) if demand > level] 194 | return 1 - sum(stockout_periods) / len(self.variables.demands) 195 | 196 | def calculate_total_cost(self): 197 | total_holding_cost = sum(self.variables.holding_cost) 198 | total_ordering_cost = self.variables.num_orders_placed * self.constants.ordering_cost 199 | return total_holding_cost + total_ordering_cost + self.variables.backlog_cost 200 | 201 | 202 | def run(simulation:Inventory_Simulation,TIME): 203 | simulation.env.process(simulation.runner_setup()) 204 | simulation.env.process(simulation.observe()) 205 | simulation.env.run(until=TIME) 206 | 207 | constants = Constants(500, 2000, 1000, 2000, 10, 20, 2) 208 | variables = Variables(constants) 209 | functions = ExternalFunctions() 210 | 211 | env = simpy.Environment() 212 | inventory_simulation = Inventory_Simulation(env, constants, variables, functions) 213 | 214 | 215 | run(inventory_simulation, 10000) 216 | 217 | print(f"Service Level: {inventory_simulation.service_level()}") 218 | print(f"Total Holding Cost: {inventory_simulation.total_holding_cost()}") 219 | print(f"Fill Rate: {inventory_simulation.calculate_fill_rate()}") 220 | print(f"Cycle Rate: {inventory_simulation.calculate_cycle_rate()}") 221 | print(f"Service Level (Alpha): {inventory_simulation.service_level_alpha()}") 222 | print(f"Total Cost: {inventory_simulation.calculate_total_cost()}") 223 | print(f"Fill Rate: {inventory_simulation.calculate_fill_rate()}") 224 | print(f"Cycle Rate: {inventory_simulation.calculate_cycle_rate()}") 225 | 226 | 227 | inventory_simulation.plot('inventory') 228 | inventory_simulation.plot('balance') 229 | inventory_simulation.plot('holding') 230 | 231 | 232 | def run_simulation_reorder(reorder_level: int, reorder_qty: int, purchase_cost: float, selling_price: float, holding_cost_unit: float, ordering_cost: float, other_costs: float, lead_time_mean: float, lead_time_std: float, delivery_batches: int, daily_demand_mean: float, daily_demand_std: float, review_period: int, backlog_cost_unit: float, safety_stock: int, balance: int, TIME: int,return_plot:bool=False) -> Tuple[float, float, float, float]: 233 | 234 | 235 | 236 | 237 | 238 | constants = Constants(reorder_level, reorder_qty, purchase_cost, selling_price, holding_cost_unit, ordering_cost, other_costs, lead_time_mean, lead_time_std, delivery_batches, daily_demand_mean, daily_demand_std, review_period, backlog_cost_unit, safety_stock, balance) 239 | variables = Variables(constants) 240 | functions = ExternalFunctions() 241 | 242 | env = simpy.Environment() 243 | inventory_simulation = Inventory_Simulation(env, constants, variables, functions) 244 | run(inventory_simulation, TIME) 245 | 246 | cost_of_inventory = inventory_simulation.avg_cost_of_inventory() 247 | cycle_service_level = inventory_simulation.calculate_cycle_rate() 248 | fill_rate = inventory_simulation.calculate_fill_rate() 249 | total_profit = inventory_simulation.calculate_profit() 250 | plots = inventory_simulation.plot_all_inventory() 251 | 252 | if return_plot: 253 | return cost_of_inventory, cycle_service_level, fill_rate, total_profit, plots 254 | else: 255 | return cost_of_inventory, cycle_service_level, fill_rate, total_profit -------------------------------------------------------------------------------- /discrete_event_simulation/single/simulation_classes.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from dataclasses import dataclass, field 3 | from typing import Optional 4 | import numpy as np 5 | from typing import List, Callable 6 | from math import ceil 7 | import matplotlib.pyplot as plt 8 | import pandas as pd 9 | 10 | 11 | @dataclass 12 | class Constants: 13 | 14 | reorder_level: Optional[int] = 10 15 | reorder_qty: Optional[int] = 20 16 | purchase_cost: Optional[int] = 1000 17 | selling_price: Optional[int] = 2000 18 | 19 | holding_cost_unit: Optional[int] = 2 20 | ordering_cost: Optional[int] = 1 21 | other_costs: Optional[int] = 4 22 | lead_time_mean: Optional[int] = 53 23 | lead_time_std: Optional[int] = 20 24 | delivery_batches: Optional[int] = 1 25 | daily_demand_mean: Optional[int] = 0.0813 26 | daily_demand_std: Optional[int] = 12 27 | review_period : Optional[int] = 1 28 | backlog_cost_unit : Optional[int] = 3 29 | safety_stock : Optional[int] = 0 30 | balance : Optional[int] = 0 31 | 32 | @dataclass 33 | class Variables: 34 | constants : Constants = Constants(10, 20, 1000, 2000, 10, 20, 2) 35 | safety_stock: int = constants.safety_stock 36 | balance: float = constants.balance 37 | num_ordered: int = 0 38 | inventory: float = field(default_factory=lambda: np.random.uniform(0, constants.reorder_qty, 1).item()) 39 | obs_time: List[float] = field(default_factory=list) 40 | inventory_level: List[float] = field(default_factory=list) 41 | demand: float = constants.daily_demand_mean 42 | costslevel: List[float] = field(default_factory=list) 43 | saleslevel: List[float] = field(default_factory=list) 44 | holding_cost: List[float] = field(default_factory=list) 45 | service_outage_cnt: int = 0 46 | demands: List[float] = field(default_factory=list) 47 | num_orders_placed : int = 0 # number of orders placed 48 | backlog_cost : int = constants.backlog_cost_unit 49 | 50 | def __post_init__(self): 51 | self.safety_stock = int(0.2*constants.reorder_level) 52 | 53 | 54 | 55 | 56 | 57 | @dataclass 58 | class Setting: 59 | reorder_level : float = 10 60 | reorder_qty : float = 20 61 | purchase_cost : float = 1000 62 | selling_price : float = 2000 63 | 64 | holding_cost_unit : float = 2 65 | ordering_cost : float = 1 66 | other_costs : float = 4 67 | lead_time_mean : float = 53 68 | lead_time_std : float = 20 69 | delivery_batches : float = 1 70 | daily_demand_mean : float = 0.0813 71 | daily_demand_std : float = 12 72 | review_period : float = 1 73 | backlog_cost_unit : float = 3 74 | safety_stock : float = 0 75 | balance : float = 0 76 | TIME : int = 1000 77 | 78 | 79 | 80 | @dataclass 81 | class ExternalFunctions: 82 | generate_interarrival: Callable = np.random.exponential 83 | generate_leadtime: Callable = np.random.normal 84 | generate_demand: Callable = np.random.normal 85 | 86 | constants = Constants(10, 20, 1000, 2000, 10, 20, 2) 87 | variables = Variables(constants) 88 | functions = ExternalFunctions() 89 | -------------------------------------------------------------------------------- /gen_algo_simulation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/__init__.py -------------------------------------------------------------------------------- /gen_algo_simulation/plotter.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import pandas as pd 3 | import os 4 | SAVE_FOLDER = "/mnt/d/Portfolio/Inventory_Optimization/inventory_optimizer_app/gen_algo_simulation/single/plots" 5 | 6 | def plot_inventory(on_hand, demand, in_transit): 7 | print("start prinitng") 8 | fig, axs = plt.subplots(3, figsize=(12, 15)) 9 | 10 | 11 | axs[0].plot(on_hand) 12 | axs[0].set_title('On-Hand Inventory Over Time') 13 | axs[0].set_xlabel('Time') 14 | axs[0].set_ylabel('On-hand inventory') 15 | 16 | 17 | axs[1].plot(demand) 18 | axs[1].set_title('Demand Over Time') 19 | axs[1].set_xlabel('Time') 20 | axs[1].set_ylabel('Demand') 21 | 22 | in_transit_total = in_transit.sum(axis=1) 23 | axs[2].plot(in_transit_total) 24 | axs[2].set_title('In-Transit Inventory Over Time') 25 | axs[2].set_xlabel('Time') 26 | axs[2].set_ylabel('In-transit inventory') 27 | 28 | 29 | 30 | 31 | 32 | fig.tight_layout() 33 | #save figure to file 34 | fig.savefig(os.path.join(SAVE_FOLDER,"inventory.png")) 35 | 36 | fig.tight_layout() 37 | 38 | return fig 39 | 40 | def print_results(results): 41 | df = pd.DataFrame(results) 42 | df.columns = ["cost_of_inventory", 43 | "cycle_service_level", 44 | "fill_rate", 45 | "total_profit", 46 | "SL_alpha", 47 | "c_k", 48 | "c_h", 49 | "c_b", 50 | ] 51 | 52 | df.to_csv(os.path.join(SAVE_FOLDER,"results.csv")) 53 | 54 | -------------------------------------------------------------------------------- /gen_algo_simulation/simulation_settings.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class Setting(Enum): 4 | GENERATIONS = 100 5 | POPULATION_SIZE = 50 6 | MUTATION_RATE = 0.1 7 | TIME = 10000 8 | PARAMETER_ONE_LIMITS = (1,13) 9 | PARAMETER_TWO_LIMITS = (1,101) 10 | -------------------------------------------------------------------------------- /gen_algo_simulation/single/Demand.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numba import njit 3 | 4 | from random import random 5 | 6 | @njit 7 | def choice(probabilities): 8 | cumulative = 0 9 | s = np.random.random() 10 | for i in range(len(probabilities)): 11 | cumulative += probabilities[i] 12 | if s < cumulative: 13 | return i 14 | return len(probabilities) - 1 15 | 16 | 17 | 18 | @njit 19 | def standard_deviation(data: np.array) -> float: 20 | data = np.array(data,dtype=np.float64) 21 | mean_data = 0 22 | for i in range(len(data)): 23 | mean_data += data[i] 24 | mean_data = mean_data / len(data) 25 | sum_of_squares = np.sum((data - mean_data)**2) 26 | return np.sqrt(sum_of_squares / (len(data) - 1)) 27 | 28 | 29 | 30 | @njit 31 | def attributes(pmf: np.ndarray, x: np.ndarray) -> tuple: 32 | mean = np.sum(pmf * x) 33 | std = np.sqrt(np.sum(x**2 * pmf) - np.sum(pmf * x)**2) 34 | return mean, std 35 | 36 | 37 | @njit 38 | def min_max(data:np.ndarray): 39 | min_val = data[0] 40 | max_val = data[0] 41 | 42 | for val in data: 43 | if val < min_val: 44 | min_val = val 45 | if val > max_val: 46 | max_val = val 47 | 48 | return min_val, max_val 49 | 50 | 51 | @njit 52 | def histogram(data:np.ndarray, bins:np.ndarray): 53 | min_data, max_data = min_max(data) 54 | bin_width = (max_data - min_data) / bins 55 | bin_counters = np.zeros(bins,) 56 | bin_edges = np.zeros(bins+1) 57 | # initialize bin counters 58 | for i in range(bins): 59 | bin_counters[i] = 0 60 | 61 | # calculating the bin each data point belongs to 62 | for val in data: 63 | bin_index = min(int((val - min_data) / bin_width), bins - 1) 64 | bin_counters[bin_index] += 1 65 | 66 | # defining bin edges 67 | for i in range(bins+1): 68 | bin_edges[i] = min_data + i*bin_width 69 | 70 | return bin_counters, bin_edges 71 | 72 | 73 | 74 | @njit 75 | def estimate_pmf(x:np.ndarray, bins:int=100): 76 | 77 | 78 | hist, bin_edges = histogram(x, bins) 79 | bin_centers = 0.5*(bin_edges[:-1] + bin_edges[1:]) 80 | pmf = hist / np.sum(hist) 81 | return pmf, bin_centers 82 | 83 | 84 | @njit 85 | def get_range_values(x: np.array) -> tuple: 86 | bandwidth = standard_deviation(x) / (len(x)**(1/5)) 87 | #find min of x 88 | min_x = 0 89 | for i in range(len(x)): 90 | if x[i] < min_x: 91 | min_x = x[i] 92 | max_x = 0 93 | for i in range(len(x)): 94 | if x[i] > max_x: 95 | max_x = x[i] 96 | lower = np.floor(min_x - 3 * bandwidth) 97 | upper = np.ceil(max_x + 3 * bandwidth) 98 | return lower, upper 99 | 100 | 101 | 102 | @njit 103 | def model_demand(data: np.ndarray, time: int) -> tuple: 104 | # Calculate bandwidth 105 | 106 | # Get x values for density estimation 107 | lower, upper = get_range_values(data) 108 | x_kde = np.linspace(lower, upper, 1000) 109 | 110 | pmf, x_kde = estimate_pmf(data, bins=1000) 111 | 112 | # Compute mean and standard deviation 113 | demand_mu, demand_std = attributes(pmf, x_kde) 114 | 115 | # Generate demand using the estimated pmf 116 | indices = np.array([choice(pmf) for _ in range(time)]) 117 | demand = x_kde[indices] 118 | 119 | # Ensure positive demand 120 | demand = np.maximum(0, demand) 121 | 122 | return demand, demand_mu, demand_std, pmf 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /gen_algo_simulation/single/Leadtime.py: -------------------------------------------------------------------------------- 1 | from numba import njit 2 | import pandas as pd 3 | import numpy as np 4 | import numba 5 | 6 | def leadtime_generated_params(): 7 | L_x = np.array([3,4,5]) 8 | L_pmf = np.array([0.1,0.7,0.2]) 9 | L_mu, L_std = attributes_numba(L_pmf, L_x) 10 | L_median = 4 11 | L_max = 5 12 | 13 | return L_x, L_pmf, L_mu, L_std, L_median, L_max 14 | 15 | 16 | @njit 17 | def attributes_numba(pmf: np.ndarray, x: np.ndarray) -> tuple: 18 | """ 19 | Returns the mean and standard deviation of a distribution. 20 | 21 | Parameters 22 | ---------- 23 | pmf : distribution 24 | The distribution as a pmf (probability mass function). 25 | x : array 26 | The values for which the distribution is defined. 27 | 28 | Returns 29 | ------- 30 | mean : float 31 | The mean of the distribution. 32 | std : float 33 | The standard deviation of the distribution. 34 | 35 | """ 36 | mean = np.sum(pmf * x) 37 | std = np.sqrt(np.sum(x ** 2 * pmf) - np.sum(pmf * x) ** 2) 38 | return mean, std 39 | 40 | 41 | 42 | 43 | @numba.jit(nopython=True) 44 | def compute_leadtime_data(L_x_orig): 45 | """ 46 | The function will find the probability mass function of the lead time data and return the mean, standard deviation, median and max of the lead time data. 47 | """ 48 | # Create a new L_x array that includes all integers within the range of L_x_orig 49 | L_x = np.arange(np.min(L_x_orig), np.max(L_x_orig) + 1) 50 | 51 | bins = np.arange(int(np.min(L_x_orig)), int(np.max(L_x_orig)) + 2) 52 | hist, bin_edges = np.histogram(L_x_orig, bins=bins) 53 | 54 | # Create a new L_pmf array that is the same length as L_x. For the bins not present in hist, set the value to 0 55 | L_pmf = np.zeros_like(L_x, dtype=np.float64) 56 | L_pmf[:len(hist)] = hist 57 | 58 | # Normalize the histogram manually since density argument is not supported in Numba 59 | L_pmf = L_pmf / L_pmf.sum() 60 | 61 | bin_mids = bin_edges[:-1] + np.diff(bin_edges)/2 62 | L_mu, L_std = attributes_numba(L_pmf, bin_mids) 63 | 64 | L_median = np.median(L_x_orig) 65 | L_max = np.max(L_x_orig) 66 | 67 | return L_x, L_pmf, L_mu, L_std, L_median, L_max 68 | 69 | 70 | def get_leadtime_from_df(lead_time_df_path:str, colname_target:str): 71 | df = pd.read_csv(lead_time_df_path) 72 | L_x_orig = np.array(df[colname_target]) 73 | L_x, L_pmf, L_mu, L_std, L_median, L_max = compute_leadtime_data(L_x_orig) 74 | 75 | return L_x, L_pmf, L_mu, L_std, L_median, L_max 76 | 77 | @njit 78 | def get_leadtime_from_array(lead_time_array:np.ndarray): 79 | 80 | L_x_orig = np.array(lead_time_array) 81 | L_x, L_pmf, L_mu, L_std, L_median, L_max = compute_leadtime_data(L_x_orig) 82 | 83 | return L_x, L_pmf, L_mu, L_std, L_median, L_max 84 | -------------------------------------------------------------------------------- /gen_algo_simulation/single/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/single/__init__.py -------------------------------------------------------------------------------- /gen_algo_simulation/single/api/sim_api.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/single/api/sim_api.py -------------------------------------------------------------------------------- /gen_algo_simulation/single/continous/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/single/continous/__init__.py -------------------------------------------------------------------------------- /gen_algo_simulation/single/continous/optimizer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('/mnt/d/Portfolio/Inventory_Optimization/inventory_optimizer_app') 3 | import numpy as np 4 | import pandas as pd 5 | from gen_algo_simulation.single.Demand import * 6 | from gen_algo_simulation.single.Leadtime import * 7 | from gen_algo_simulation.single.continous.simulation import * 8 | 9 | 10 | from numba import njit 11 | from numba import njit 12 | import numpy as np 13 | from gen_algo_simulation.simulation_settings import * 14 | from numba import njit 15 | import numpy as np 16 | 17 | 18 | GENERATIONS = None 19 | POPULATION_SIZE = None 20 | MUTATION_RATE = None 21 | TIME = None 22 | PARAMETER_ONE_LIMITS = None 23 | PARAMETER_TWO_LIMITS = None 24 | 25 | GENERATIONS = Setting.GENERATIONS.value 26 | POPULATION_SIZE = Setting.POPULATION_SIZE.value 27 | MUTATION_RATE = Setting.MUTATION_RATE.value 28 | TIME = Setting.TIME.value 29 | PARAMETER_ONE_LIMITS = Setting.PARAMETER_ONE_LIMITS.value 30 | PARAMETER_TWO_LIMITS = Setting.PARAMETER_TWO_LIMITS.value 31 | 32 | def setup_optimizer(ROP:int,EOQ:int,time:int,demand:List[float],lead_times:List[float],cost_per_transcation:float,hold_cost_per_unit:float,backlog_cost:float,sale_price:float,cost_price:float,tax_rate:float): 33 | return ROP,EOQ,time,demand,lead_times,cost_per_transcation,hold_cost_per_unit,backlog_cost,sale_price,cost_price,tax_rate 34 | 35 | 36 | 37 | @njit 38 | def cumsum(list_x:List[float]) -> List[float]: 39 | for i in range(1,len(list_x)): 40 | list_x[i] = list_x[i-1] + list_x[i] 41 | return list_x 42 | 43 | @njit 44 | def random_choice_numba(a, p): 45 | return a[np.searchsorted(cumsum(p), np.random.random(), side="right")] 46 | 47 | @njit 48 | def init_population(pop_size): 49 | population = np.zeros((pop_size, 2), dtype=np.int64) 50 | for i in range(pop_size): 51 | population[i, 0] = np.random.randint(1, 13) # Review period 52 | population[i, 1] = np.random.randint(1, 101) # Safety stock 53 | return population 54 | 55 | # Calculate fitness (the lower the cost, the higher the fitness) 56 | @njit 57 | def fitness(individual, time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate): 58 | cost, _, _, _ = simulation_process_single_item(individual[0], individual[1], time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 59 | if cost == 0: 60 | return np.inf # return a large fitness value for zero cost 61 | else: 62 | return 1 / (1+cost) 63 | # Perform selection 64 | @njit 65 | def selection(population, fitnesses): 66 | parents = [] 67 | for _ in range(POPULATION_SIZE // 2): 68 | # Select two parents randomly, with probability proportional to their fitness 69 | parent1 = np.random.choice(population, p=fitnesses) 70 | parent2 = np.random.choice(population, p=fitnesses) 71 | parents.append(parent1) 72 | parents.append(parent2) 73 | return parents 74 | 75 | # Perform crossover 76 | @njit 77 | def crossover(parent1, parent2): 78 | crossover_index = np.random.randint(0, len(parent1)) 79 | child1 = np.concatenate((parent1[:crossover_index], parent2[crossover_index:])) 80 | child2 = np.concatenate((parent2[:crossover_index], parent1[crossover_index:])) 81 | return child1, child2 82 | 83 | # Perform mutation 84 | @njit 85 | def mutation(individual, mutation_rate): 86 | if np.random.rand() < mutation_rate: 87 | index = np.random.randint(0, 2) 88 | if index == 0: # Review period 89 | individual[index] = np.random.randint(PARAMETER_ONE_LIMITS[0], PARAMETER_ONE_LIMITS[1] + 1) 90 | else: # Safety stock 91 | individual[index] = np.random.randint(PARAMETER_TWO_LIMITS[0], PARAMETER_TWO_LIMITS[1] + 1) 92 | return individual 93 | 94 | # Genetic algorithm 95 | @njit 96 | def find_max_list(list): 97 | max = list[0] 98 | for i in list: 99 | if i > max: 100 | max = i 101 | return max 102 | @njit 103 | def find_argmax_list(list): 104 | max = list[0] 105 | argmax = 0 106 | for i in range(len(list)): 107 | if list[i] > max: 108 | max = list[i] 109 | argmax = i 110 | return argmax 111 | 112 | @njit 113 | def genetic_algorithm(generations, population_size, time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate, no_improvement_limit=3, mutation_rate=0.2): 114 | population = init_population(population_size) 115 | population_fitnesses = np.zeros(population_size) 116 | 117 | # calculate fitness for each individual in the population 118 | for i in range(population_size): 119 | population_fitnesses[i] = fitness(population[i], time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 120 | 121 | best_fitness = np.max(population_fitnesses) 122 | best_individual = population[np.argmax(population_fitnesses)] 123 | no_improvement_count = 0 124 | 125 | for _ in range(generations): 126 | new_population = np.zeros((population_size, 2), dtype=np.int64) 127 | new_population_fitnesses = np.zeros(population_size) 128 | for idx in range(population_size // 2): 129 | # select two parents 130 | parent1 = random_choice_numba(population, population_fitnesses) 131 | parent2 = random_choice_numba(population, population_fitnesses) 132 | 133 | # perform crossover and mutation 134 | child1, child2 = crossover(parent1, parent2) 135 | children = [mutation(child1, mutation_rate), mutation(child2, mutation_rate)] 136 | 137 | # add children to new population 138 | for j, child in enumerate(children): 139 | new_population[2 * idx + j] = child 140 | new_population_fitnesses[2 * idx + j] = fitness(child, time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 141 | 142 | best_index = np.argmax(new_population_fitnesses) 143 | current_best_fitness = new_population_fitnesses[best_index] 144 | current_best_individual = new_population[best_index] 145 | 146 | if current_best_fitness > best_fitness: 147 | best_fitness = current_best_fitness 148 | best_individual = current_best_individual 149 | no_improvement_count = 0 150 | else: 151 | no_improvement_count += 1 152 | if no_improvement_count >= no_improvement_limit: 153 | break 154 | 155 | # prepare for next generation 156 | population = new_population 157 | population_fitnesses = new_population_fitnesses 158 | 159 | return best_individual 160 | 161 | 162 | 163 | def random_search_optimizer(PARAMETER_ONE_LIMITS,PARAMETER_TWO_LIMITS,time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate): 164 | best_fitness = 0 165 | best_individual = None 166 | for _ in range(100): 167 | individual = np.zeros(2, dtype=np.int64) 168 | individual[0] = np.random.randint(PARAMETER_ONE_LIMITS[0], PARAMETER_ONE_LIMITS[1] + 1) 169 | individual[1] = np.random.randint(PARAMETER_TWO_LIMITS[0], PARAMETER_TWO_LIMITS[1] + 1) 170 | individual_fitness = fitness(individual, time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 171 | if individual_fitness > best_fitness: 172 | best_fitness = individual_fitness 173 | best_individual = individual 174 | return best_individual 175 | 176 | """ 177 | try: 178 | best_individual = genetic_algorithm(GENERATIONS, POPULATION_SIZE, time, demand,lead_times,cost_per_transcation,hold_cost_per_unit,backlog_cost,sale_price,cost_price,tax_rate) 179 | 180 | except: 181 | best_individual = random_search_optimizer(PARAMETER_ONE_LIMITS,PARAMETER_TWO_LIMITS,time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 182 | print(f" found results as ROL = {best_individual[0]}, EOQ = {best_individual[1]}") 183 | """ -------------------------------------------------------------------------------- /gen_algo_simulation/single/continous/setup_simulation.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/single/continous/setup_simulation.py -------------------------------------------------------------------------------- /gen_algo_simulation/single/continous/simulation.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('/mnt/d/Portfolio/Inventory_Optimization/inventory_optimizer_app') 3 | import numpy as np 4 | import pandas as pd 5 | from gen_algo_simulation.single.Demand import * 6 | from gen_algo_simulation.single.Leadtime import * 7 | from gen_algo_simulation.plotter import * 8 | import numpy as np 9 | import pandas as pd 10 | from numba import njit 11 | from typing import List 12 | 13 | 14 | @njit 15 | def random_choice_numba(a, p): 16 | # a is array of choices 17 | # p is array of probabilities 18 | cumsum = np.cumsum(p) 19 | random_number = np.random.random() 20 | index = np.searchsorted(cumsum, random_number, side="right") 21 | return a[index] 22 | 23 | @njit 24 | def run_simulation(time, ROP, EOQ, transit, hand, unit_shorts, stockout_period, L_x, L_pmf, demand, 25 | cost_per_transcation, sale_price, cost_price, tax_rate, backlog_cost,physical_stock,hold_cost_per_unit): 26 | c_k = cost_per_transcation 27 | c_h = hold_cost_per_unit * demand[0] 28 | c_b = backlog_cost 29 | R = 1 30 | 31 | for t in range(time): 32 | 33 | hand[t] = hand[t-1] - demand[t] + transit[t-1,0] 34 | 35 | if t < time-1: 36 | transit[t+1,:-1] = transit[t,1:] 37 | if t % R == 0: 38 | net = hand[t] + transit[t].sum() 39 | if net <= ROP: 40 | actual_L = random_choice_numba(L_x, p=L_pmf) 41 | transit[t+1,actual_L-1] = (1+ (ROP-net)//EOQ)*EOQ 42 | 43 | if hand[t] > 0: 44 | physical_stock[t] = (hand[t-1] + transit[t-1,0] + hand[t])/2 45 | else: 46 | physical_stock[t] = max(hand[t-1] + transit[t-1,0],0)**2/max(demand[t],1)/2 47 | 48 | 49 | if demand[t] == 0: 50 | unit_shorts[t] = 0 51 | 52 | elif hand[t] < 0: 53 | unit_shorts[t] = demand[t] 54 | 55 | elif demand[t]> hand[t] and hand[t] >= 0: 56 | unit_shorts[t] = demand[t] - hand[t] 57 | else: 58 | unit_shorts[t] = 0 59 | 60 | 61 | stockout_period[t] = hand[t] < demand[t] 62 | 63 | 64 | 65 | 66 | c_k = cost_per_transcation if hand[t] + transit[t].sum() <= ROP else 0 67 | 68 | c_h += (hand[t] + transit[t].sum()) * hold_cost_per_unit 69 | 70 | c_b += backlog_cost * unit_shorts[t] 71 | 72 | 73 | 74 | sales = min(hand[t], demand[t]) 75 | 76 | revenue = sales * sale_price 77 | 78 | costs = sales * cost_price 79 | 80 | gross_profit = revenue - costs 81 | 82 | net_profit = gross_profit * (1 - tax_rate) 83 | total_profit = net_profit - (c_k + c_h) 84 | stockout_cycle = stockout_period.sum() 85 | 86 | cost_of_inventory = (c_k + c_h + c_b)/time 87 | 88 | SL_alpha = 1 - stockout_cycle / time 89 | 90 | fill_rate = 1 - (unit_shorts.sum() / demand.sum()) 91 | cycle_service_level = 1 - stockout_cycle / len(demand) 92 | over_stock = (hand[t] + transit[t].sum()) - demand[t] 93 | over_stock_percent = over_stock / demand.sum() 94 | 95 | 96 | return c_k, c_h,c_b, demand, hand, transit,total_profit,SL_alpha,fill_rate,cost_of_inventory,cycle_service_level 97 | 98 | @njit 99 | def simulation(ROP,EOQ , time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate): 100 | 101 | demand, demand_mu, demand_std, pmf = model_demand(demand,time) 102 | L_x, L_pmf, L_mu, L_std, L_median, L_max = get_leadtime_from_array(lead_times) 103 | 104 | hand = np.zeros(time) 105 | 106 | transit = np.zeros((time, L_max + 1)) 107 | unit_shorts = np.zeros(time) 108 | stockout_period = np.full(time, False) 109 | physical_stock = np.zeros(time) 110 | 111 | 112 | 113 | 114 | return run_simulation(time, ROP, EOQ, transit, hand, unit_shorts, stockout_period, L_x, 115 | L_pmf, demand, cost_per_transcation, sale_price, 116 | cost_price, tax_rate,backlog_cost, physical_stock, hold_cost_per_unit) 117 | 118 | 119 | 120 | 121 | @njit 122 | def simulation_process_single_item(ROP:int,EOQ:int,time:int,demand:List[float],lead_times:List[float],cost_per_transcation:float,hold_cost_per_unit:float,backlog_cost:float,sale_price:float,cost_price:float,tax_rate:float): 123 | 124 | c_k, c_h,c_b, demand, hand, transit,total_profit,SL_alpha,fill_rate,cost_of_inventory,cycle_service_level = simulation(ROP,EOQ , time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 125 | 126 | return cost_of_inventory, cycle_service_level, fill_rate, total_profit,demand,hand,transit 127 | 128 | 129 | 130 | 131 | @njit 132 | def simulation_process_single_item_all(ROP:int,EOQ:int,time:int,demand:List[float],lead_times:List[float],cost_per_transcation:float,hold_cost_per_unit:float,backlog_cost:float,sale_price:float,cost_price:float,tax_rate:float): 133 | 134 | c_k, c_h,c_b, demand, hand, transit,total_profit,SL_alpha,fill_rate,cost_of_inventory,cycle_service_level = simulation(ROP,EOQ , time, demand, lead_times, cost_per_transcation, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 135 | 136 | return c_k, c_h,c_b, demand, hand, transit,total_profit,SL_alpha,fill_rate,cost_of_inventory,cycle_service_level 137 | 138 | 139 | #print(demand) 140 | 141 | """ 142 | c_k, c_h, transit, hand, unit_shorts, stockout_period, physical_stock, total_profit,stockout_cycle,avg_cost = simulation(ROP,EOQ,time,item_prop1) 143 | 144 | print("Total Profit:",total_profit) 145 | print("Cost:",c_k + c_h) 146 | print("SL_alpha:",1 - stockout_cycle / len(demand)) 147 | print("fill_rate:",1 - unit_shorts.sum() / demand.sum()) 148 | print("Stockout Cycle:",stockout_cycle) 149 | print("Total Profit:",total_profit) 150 | print("Unit shorts:",unit_shorts.sum()) 151 | print("Avg Cost:",avg_cost) 152 | 153 | 154 | def print_cost_results(c_k,c_h,stockout_cycle,demand): 155 | 156 | cost = c_k + c_h 157 | SL_alpha = 1 - stockout_cycle / len(demand) # using simulated demand 158 | fill_rate = 1 - unit_shorts.sum() / demand.sum() # using simulated demand 159 | 160 | print("Total Profit:",total_profit) 161 | print("Cost:",cost) 162 | print("SL_alpha:",SL_alpha) 163 | print("fill_rate:",fill_rate) 164 | 165 | 166 | plot_inventory(hand, demand, transit) 167 | 168 | """ -------------------------------------------------------------------------------- /gen_algo_simulation/single/plots/inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad1287/Inventory-Optimization-with-DES-simulation-and-Genetic-Algorithms/c95562e1d043f649cf2b4fc373839a672ad7fce9/gen_algo_simulation/single/plots/inventory.png -------------------------------------------------------------------------------- /gen_algo_simulation/single/streamlit/app.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | 3 | import numpy as np 4 | from simulation_settings import * 5 | 6 | from plotter import * 7 | 8 | from Leadtime import * 9 | 10 | from Demand import model_demand 11 | 12 | from numba import njit 13 | from gen_algo_simulation.single.continous.simulation import * 14 | 15 | 16 | import pandas as pd 17 | 18 | def run_app(): 19 | 20 | st.title("Inventory Simulation") 21 | 22 | # Inputs from user 23 | demand = st.text_input("Enter demand (comma-separated numbers): ") 24 | demand = [int(i) for i in demand.split(",")] 25 | 26 | lead_time = st.text_input("Enter lead time (comma-separated numbers): ") 27 | lead_time = [int(i) for i in lead_time.split(",")] 28 | 29 | ROP = st.number_input("Enter ROP: ", min_value=0, step=1) 30 | EOQ = st.number_input("Enter EOQ: ", min_value=0, step=1) 31 | TIME = st.number_input("Enter TIME: ", min_value=0, step=1) 32 | 33 | cost_per_transaction = st.number_input("Enter cost per transaction: ", min_value=0.0, step=0.01) 34 | hold_cost_per_unit = st.number_input("Enter holding cost per unit: ", min_value=0.0, step=0.01) 35 | backlog_cost = st.number_input("Enter backlog cost: ", min_value=0.0, step=0.01) 36 | sale_price = st.number_input("Enter sale price: ", min_value=0.0, step=0.01) 37 | cost_price = st.number_input("Enter cost price: ", min_value=0.0, step=0.01) 38 | tax_rate = st.number_input("Enter tax rate: ", min_value=0.0, step=0.01) 39 | 40 | # Once all inputs are given, we run the simulation 41 | if st.button("Run Simulation"): 42 | demand, demand_mu, demand_std, pmf = model_demand(demand,TIME) 43 | L_x, L_pmf, L_mu, L_std, L_median, L_max = get_leadtime_from_array(lead_time) 44 | 45 | 46 | 47 | c_k, c_h,c_b, demand, hand, transit,total_profit,SL_alpha,fill_rate,cost_of_inventory,cycle_service_level = simulation(ROP,EOQ,TIME,demand,lead_time,cost_per_transaction,hold_cost_per_unit,backlog_cost,sale_price,cost_price,tax_rate) 48 | 49 | 50 | st.write(f"SL_alpha: {SL_alpha}") 51 | st.write(f"fill_rate: {fill_rate}") 52 | st.write(f"Total Profit: {total_profit}") 53 | st.write(f"Cost of Inventory: {cost_of_inventory}") 54 | st.write(f"Cycle Service Level: {cycle_service_level}") 55 | 56 | fig_inventory = plot_inventory(hand, demand, transit) 57 | st.pyplot(fig_inventory) 58 | 59 | 60 | 61 | if __name__ == "__main__": 62 | run_app() -------------------------------------------------------------------------------- /gen_simulation_streamlit.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import numpy as np 3 | 4 | from gen_algo_simulation.simulation_settings import * 5 | from gen_algo_simulation.single.continous.simulation import * 6 | from gen_algo_simulation.plotter import * 7 | 8 | 9 | GENERATIONS = Setting.GENERATIONS.value 10 | POPULATION_SIZE = Setting.POPULATION_SIZE.value 11 | MUTATION_RATE = Setting.MUTATION_RATE.value 12 | TIME = Setting.TIME.value 13 | PARAMETER_ONE_LIMITS = Setting.PARAMETER_ONE_LIMITS.value 14 | PARAMETER_TWO_LIMITS = Setting.PARAMETER_TWO_LIMITS.value 15 | 16 | 17 | # Simulation input fields 18 | ROP = st.number_input('ROP', value=10) 19 | EOQ = st.number_input('EOQ', value=100) 20 | TIME = st.number_input('TIME', value=100) 21 | demand = st.text_input('Demand (comma-separated)', '10,20,30,40,50,60,70,80,90,100') 22 | lead_times = st.text_input('Lead times (comma-separated)', '1,2,3,4,5,6,7,8,9,10') 23 | cost_per_transaction = st.number_input('Cost per transaction', value=1.0) 24 | hold_cost_per_unit = st.number_input('Hold cost per unit', value=1.0) 25 | backlog_cost = st.number_input('Backlog cost', value=1.0) 26 | sale_price = st.number_input('Sale price', value=10.0) 27 | cost_price = st.number_input('Cost price', value=5.0) 28 | tax_rate = st.number_input('Tax rate', value=0.2) 29 | 30 | # Convert string inputs to list 31 | demand = list(map(int, demand.split(','))) 32 | lead_times = list(map(int, lead_times.split(','))) 33 | 34 | if st.button('Run Simulation'): 35 | cost_of_inventory, cycle_service_level, fill_rate, total_profit,demand,hand,transit = simulation_process_single_item(ROP, EOQ, TIME, demand, lead_times, cost_per_transaction, hold_cost_per_unit, backlog_cost, sale_price, cost_price, tax_rate) 36 | 37 | # Display the results 38 | st.write(f'Cost of Inventory: {cost_of_inventory}') 39 | st.write(f'Cycle Service Level: {cycle_service_level}') 40 | st.write(f'Fill Rate: {fill_rate}') 41 | st.write(f'Total Profit: {total_profit}') 42 | 43 | 44 | plot_object = plot_inventory(hand, demand, transit) 45 | st.pyplot(plot_object) 46 | 47 | 48 | -------------------------------------------------------------------------------- /gen_single_item_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel, Field 3 | import numpy as np 4 | 5 | from gen_algo_simulation.simulation_settings import * 6 | 7 | from gen_algo_simulation.single.continous.simulation import * 8 | 9 | 10 | GENERATIONS = None 11 | POPULATION_SIZE = None 12 | MUTATION_RATE = None 13 | TIME = None 14 | PARAMETER_ONE_LIMITS = None 15 | PARAMETER_TWO_LIMITS = None 16 | 17 | GENERATIONS = Setting.GENERATIONS.value 18 | POPULATION_SIZE = Setting.POPULATION_SIZE.value 19 | MUTATION_RATE = Setting.MUTATION_RATE.value 20 | TIME = Setting.TIME.value 21 | PARAMETER_ONE_LIMITS = Setting.PARAMETER_ONE_LIMITS.value 22 | PARAMETER_TWO_LIMITS = Setting.PARAMETER_TWO_LIMITS.value 23 | 24 | # Define the app 25 | app = FastAPI() 26 | 27 | # Define the request body model 28 | class SimulationConfig(BaseModel): 29 | 30 | ROP: int = Field(..., example=10) 31 | EOQ: int = Field(..., example=100) 32 | TIME: int = Field(..., example=100) 33 | demand: list = Field(..., example=[10,20,30,40,50,60,70,80,90,100]) 34 | lead_times: list = Field(..., example=[1,2,3,4,5,6,7,8,9,10]) 35 | cost_per_transcation:float = Field(..., example=1) 36 | hold_cost_per_unit:float = Field(..., example=1) 37 | backlog_cost:float = Field(..., example=1) 38 | sale_price:float = Field(..., example=10) 39 | cost_price:float = Field(..., example=5) 40 | tax_rate:float = Field(..., example=0.2) 41 | 42 | 43 | 44 | 45 | class SimulationResult(BaseModel): 46 | cost_of_inventory : float 47 | cycle_service_level : float 48 | fill_rate : float 49 | total_profit : float 50 | 51 | @app.get("/") 52 | async def root(): 53 | return {"message": "Welcome to the Fast Inventory Optimizer API!"} 54 | 55 | 56 | @app.post("/run_simulation", response_model=SimulationResult) 57 | async def run_simulation(config: SimulationConfig): 58 | # Unpack the simulation parameters from the request body 59 | 60 | ROP = config.ROP 61 | EOQ = config.EOQ 62 | TIME = config.TIME 63 | demand = config.demand 64 | lead_times = config.lead_times 65 | cost_per_transaction = config.cost_per_transcation 66 | hold_cost_per_unit = config.hold_cost_per_unit 67 | backlog_cost = config.backlog_cost 68 | sale_price = config.sale_price 69 | cost_price = config.cost_price 70 | tax_rate = config.tax_rate 71 | 72 | cost_of_inventory, cycle_service_level, fill_rate, total_profit,_,_,_ = simulation_process_single_item(ROP,EOQ,TIME,demand,lead_times,cost_per_transaction,hold_cost_per_unit,backlog_cost,sale_price,cost_price,tax_rate) 73 | # Run the simulation 74 | 75 | # Prepare the response 76 | response = SimulationResult( 77 | cost_of_inventory=cost_of_inventory, 78 | cycle_service_level=cycle_service_level, 79 | fill_rate=fill_rate, 80 | total_profit=total_profit 81 | ) 82 | 83 | # Return the response as JSON 84 | return response 85 | -------------------------------------------------------------------------------- /gen_single_item_opt_api.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel, Field 3 | import numpy as np 4 | 5 | from gen_algo_simulation.simulation_settings import * 6 | 7 | from gen_algo_simulation.single.continous.optimizer import * 8 | 9 | 10 | GENERATIONS = None 11 | POPULATION_SIZE = None 12 | MUTATION_RATE = None 13 | TIME = None 14 | PARAMETER_ONE_LIMITS = None 15 | PARAMETER_TWO_LIMITS = None 16 | 17 | GENERATIONS = Setting.GENERATIONS.value 18 | POPULATION_SIZE = Setting.POPULATION_SIZE.value 19 | MUTATION_RATE = Setting.MUTATION_RATE.value 20 | TIME = Setting.TIME.value 21 | PARAMETER_ONE_LIMITS = Setting.PARAMETER_ONE_LIMITS.value 22 | PARAMETER_TWO_LIMITS = Setting.PARAMETER_TWO_LIMITS.value 23 | 24 | # Define the app 25 | app = FastAPI() 26 | 27 | # Define the request body model 28 | class SimulationConfig(BaseModel): 29 | 30 | 31 | demand: list = Field(..., example=[10,20,30,40,50,60,70,80,90,100]) 32 | lead_times: list = Field(..., example=[1,2,3,4,5,6,7,8,9,10]) 33 | cost_per_transcation:float = Field(..., example=1) 34 | hold_cost_per_unit:float = Field(..., example=1) 35 | backlog_cost:float = Field(..., example=1) 36 | sale_price:float = Field(..., example=10) 37 | cost_price:float = Field(..., example=5) 38 | tax_rate:float = Field(..., example=0.2) 39 | 40 | 41 | 42 | 43 | class SimulationResult(BaseModel): 44 | ROP_or_ReviewPeriod : float 45 | EOQ_or_SS : float 46 | 47 | 48 | @app.get("/") 49 | async def root(): 50 | return {"message": "Welcome to the Fast Inventory Optimizer API!"} 51 | 52 | 53 | @app.post("/run_simulation", response_model=SimulationResult) 54 | async def run_simulation(config: SimulationConfig): 55 | # Unpack the simulation parameters from the request body 56 | 57 | 58 | 59 | demand = config.demand 60 | lead_times = config.lead_times 61 | cost_per_transaction = config.cost_per_transcation 62 | hold_cost_per_unit = config.hold_cost_per_unit 63 | backlog_cost = config.backlog_cost 64 | sale_price = config.sale_price 65 | cost_price = config.cost_price 66 | tax_rate = config.tax_rate 67 | best_individual = genetic_algorithm(GENERATIONS, POPULATION_SIZE, TIME, demand,lead_times,cost_per_transaction,hold_cost_per_unit,backlog_cost,sale_price,cost_price,tax_rate) 68 | 69 | # Run the simulation 70 | 71 | # Prepare the response 72 | response = SimulationResult( 73 | ROP_or_ReviewPeriod=best_individual[0], 74 | EOQ_or_SS=best_individual[1] 75 | 76 | ) 77 | 78 | # Return the response as JSON 79 | return response 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas 3 | numba 4 | scipy 5 | dask 6 | simpy 7 | streamlit 8 | fastapi 9 | uvicorn 10 | --------------------------------------------------------------------------------