{ "cells": [ { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.656332Z", "start_time": "2026-01-19T15:16:28.964012Z" } }, "cell_type": "code", "source": [ "import pandas as pd\n", "import networkx as nx" ], "id": "118e09b9de1cc31", "outputs": [], "execution_count": 1 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Understanding Domain Objects\n", "====================================\n", "\n", "ware_ops_algos represents warehouse data through **domain objects**. Understanding these is essential to use the library.\n", "\n", "Storage Locations\n", "-----------------\n", "\n", "Storage locations define where articles are physically stored in your warehouse.\n" ], "id": "21a1bad783c4a6b6" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.703895Z", "start_time": "2026-01-19T15:16:29.656332Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import Location, StorageLocations, StorageType\n", "\n", "locations_list = [\n", " Location(x=1, # Aisle number\n", " y=3, # Position along the aisle\n", " article_id=1, # Which article is stored here\n", " amount=1 # Quantity available\n", " ),\n", " Location(x=1, y=8, article_id=2, amount=1),\n", " Location(x=2, y=5, article_id=3, amount=1),\n", "]\n", "\n", "\n", "# Collection of all locations in the warehouse\n", "storage = StorageLocations(\n", " StorageType.DEDICATED, # or StorageType.SCATTERED\n", " locations=locations_list\n", ")\n", "storage.build_article_location_mapping()" ], "id": "949fd495fe54d003", "outputs": [], "execution_count": 2 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Articles\n", "------\n", "\n", "Articles represent the master data of the items stored in the warehouse." ], "id": "8b5118724f15af8e" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.708806Z", "start_time": "2026-01-19T15:16:29.703895Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import Article, Articles, ArticleType\n", "\n", "articles_list = [Article(\n", " article_id=1, # article id\n", " article_name=\"dummy_article\", # article name\n", " weight=1, # article weight\n", " volume=1 # article volume\n", "),\n", " Article(article_id=2),\n", " Article(article_id=3)\n", "] \n", "\n", "articles = Articles(tpe=ArticleType.STANDARD, articles=articles_list)" ], "id": "4f2fc1550085b3f8", "outputs": [], "execution_count": 3 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Orders\n", "------\n", "\n", "Orders represent what customers ordered and need to be picked." ], "id": "86332fa8df537c10" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.716830Z", "start_time": "2026-01-19T15:16:29.709367Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import Order, OrderPosition, OrdersDomain, OrderType\n", "\n", "# What was ordered\n", "order_positions = [\n", " OrderPosition(order_number=1, article_id=1, amount=1),\n", " OrderPosition(order_number=1, article_id=2, amount=1),\n", " OrderPosition(order_number=1, article_id=3, amount=1)\n", "]\n", "\n", "# Complete order with all positions\n", "order = Order(\n", " order_id=1,\n", " order_positions=order_positions\n", ")\n", "\n", "# Collection of all orders\n", "orders = OrdersDomain(\n", " OrderType.STANDARD,\n", " orders=[order]\n", ")" ], "id": "cd3186041deebd1d", "outputs": [], "execution_count": 4 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Resources\n", "---------\n", "\n", "Resources represent pickers (humans or robots) who fulfill orders." ], "id": "80da36b1cf07aa5c" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.723084Z", "start_time": "2026-01-19T15:16:29.716830Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import Resource, PickCart, DimensionType, Resources, ResourceType\n", "\n", "PICKER_CAPACITY = 10\n", "pick_cart = PickCart(n_dimension=1,\n", " n_boxes=1,\n", " capacities=[PICKER_CAPACITY],\n", " dimensions=[DimensionType.ITEMS])\n", "resources_list = [\n", " Resource(id=0, speed=1.0, pick_cart=pick_cart)]\n", "\n", "resources = Resources(ResourceType.HUMAN, resources_list)" ], "id": "ffbf41bac8190bdf", "outputs": [], "execution_count": 5 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Layout (for routing algorithms)\n", "--------------------------------\n", "\n", "Some routing algorithms need layout information to calculate distances.\n" ], "id": "63ed6f3feadfee87" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:29.730741Z", "start_time": "2026-01-19T15:16:29.723084Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import LayoutData, LayoutNetwork, LayoutParameters, LayoutType\n", "\n", "depot_aisle = 1\n", "start_location = (depot_aisle, -1)\n", "end_location = (depot_aisle - 1, -1)\n", "start_conn = (depot_aisle, 0)\n", "end_conn = (depot_aisle, 0)\n", "closest_node_to_start = (depot_aisle, 0)\n", "\n", "layout_params = LayoutParameters(\n", " n_aisles=10,\n", " n_pick_locations=10,\n", " dist_aisle=1,\n", " dist_pick_locations=1,\n", " dist_top_to_pick_location=1,\n", " dist_bottom_to_pick_location=1,\n", " dist_start=1,\n", " dist_end=1,\n", " start_location=start_location,\n", " end_location=end_location,\n", " n_blocks=1,\n", " start_connection_point=start_conn,\n", " end_connection_point=end_conn)\n" ], "id": "99c98ded920f6d06", "outputs": [], "execution_count": 6 }, { "metadata": {}, "cell_type": "markdown", "source": "We can now use the information defined in LayoutParams to generate a simple graph of the warehouse", "id": "809964911f3918a4" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.120952Z", "start_time": "2026-01-19T15:16:29.730741Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.generators import ShelfStorageGraphGenerator\n", " \n", "graph_gen = ShelfStorageGraphGenerator(\n", " n_aisles=layout_params.n_aisles,\n", " n_pick_locations=layout_params.n_pick_locations,\n", " dist_aisle=layout_params.dist_aisle,\n", " dist_pick_locations=layout_params.dist_pick_locations,\n", " dist_aisle_location=layout_params.dist_top_to_pick_location,\n", " dist_start=layout_params.dist_start,\n", " dist_end=layout_params.dist_end,\n", " start_location=layout_params.start_location,\n", " end_location=layout_params.end_location\n", ")\n", "\n", "graph_gen.populate_graph()" ], "id": "ab09bcd54a774530", "outputs": [], "execution_count": 7 }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.195976Z", "start_time": "2026-01-19T15:16:30.121845Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.utils.visualization import render_graph\n", "\n", "render_graph(graph_gen.G)" ], "id": "d86391fde09d354c", "outputs": [ { "data": { "text/plain": [ "
" ], "image/png": "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" }, "metadata": {}, "output_type": "display_data" } ], "execution_count": 8 }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.417493Z", "start_time": "2026-01-19T15:16:30.195976Z" } }, "cell_type": "code", "source": [ "from scipy.sparse.csgraph import floyd_warshall\n", "\n", "nodes = list(graph_gen.G.nodes())\n", "A = nx.to_scipy_sparse_array(graph_gen.G, nodelist=nodes, weight='weight', dtype=float)\n", "dist_mat, predecessors = floyd_warshall(A, directed=False, return_predecessors=True)\n", "dima = pd.DataFrame(dist_mat, index=nodes, columns=nodes)\n", " \n", "layout_network = LayoutNetwork(\n", " graph=graph_gen.G,\n", " start_node=start_location,\n", " end_node=end_location,\n", " closest_node_to_start=closest_node_to_start,\n", " min_aisle_position=0,\n", " max_aisle_position=layout_params.n_pick_locations + 1,\n", " distance_matrix=dima,\n", " predecessor_matrix=predecessors\n", ")\n", "layout = LayoutData(tpe=LayoutType.CONVENTIONAL, layout_network=layout_network, graph_data=layout_params)" ], "id": "af44906f757ec830", "outputs": [], "execution_count": 9 }, { "metadata": {}, "cell_type": "markdown", "source": "# WarehouseInfo", "id": "8a6956d3c78bebad" }, { "metadata": {}, "cell_type": "markdown", "source": "The WarehouseInfo domain object can be used to pass additional information to the domain instance that is not directly associated with any of the other domain objects. For now we only specify that it is an offline problem.", "id": "e605cb5fa91cf0e6" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.420947Z", "start_time": "2026-01-19T15:16:30.417493Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import WarehouseInfo, WarehouseInfoType\n", "\n", "warehouse_info = WarehouseInfo(tpe=WarehouseInfoType.OFFLINE)" ], "id": "34f64b608990a1e2", "outputs": [], "execution_count": 10 }, { "metadata": {}, "cell_type": "markdown", "source": "# Warehouse Domain", "id": "ae42924f935a5eef" }, { "metadata": {}, "cell_type": "markdown", "source": "Individual domain objects can be grouped in a WarehouseDomain object. We define it as an order batching and picker routing problem (OBRP)", "id": "af3f1bf1b08bb83e" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.429014Z", "start_time": "2026-01-19T15:16:30.420947Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models import BaseWarehouseDomain\n", "\n", "domain_instance = BaseWarehouseDomain(\n", " problem_class=\"OBRP\", \n", " objective=\"Distance\", \n", " layout=layout, \n", " articles=articles, \n", " orders=orders, \n", " resources=resources, \n", " storage=storage,\n", " warehouse_info=warehouse_info\n", ")" ], "id": "ab8778ee98e07d8f", "outputs": [], "execution_count": 11 }, { "metadata": {}, "cell_type": "markdown", "source": "# Algorithms", "id": "77b538003d81b717" }, { "metadata": {}, "cell_type": "markdown", "source": "ware_ops_algos implements a number of algorithms for e.g. batching, routing, scheduling etc. Each algorithm is described in the form of a model card that specifies the algorithm requirements.", "id": "fc04ac0a491346b6" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.478486Z", "start_time": "2026-01-19T15:16:30.429014Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.utils.general_functions import load_model_cards\n", "\n", "all_algorithms = load_model_cards(\"../../../src/ware_ops_algos/algorithms/opt_model_cards\")" ], "id": "ad0960ac36fea296", "outputs": [], "execution_count": 12 }, { "metadata": {}, "cell_type": "markdown", "source": "# Algorithm Filter", "id": "7c0a578b7cfadf5c" }, { "metadata": {}, "cell_type": "markdown", "source": "Based on the defined warehouse domain and the algorithm cards we can obtain the applicable algorithms for the domain. ", "id": "8a5087a50f6f6b14" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.525755Z", "start_time": "2026-01-19T15:16:30.479359Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.domain_models.taxonomy import SUBPROBLEMS\n", "from ware_ops_algos.algorithms.algorithm_filter import AlgorithmFilter\n", "\n", "filter = AlgorithmFilter(\n", " subproblems=SUBPROBLEMS,\n", ")\n", "\n", "subset = filter.filter(algorithms=all_algorithms, instance=domain_instance, verbose=True)" ], "id": "f1b1bcf0b6c080b4", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Problem type filtering for 'OBRP':\n", " Accepted types: {'routing', 'batching', 'item_assignment', 'order_selection', 'OBRP'}\n", " Result: 32/38 algorithms match\n", "\n", " Checking: ClarkAndWrightNN\n", " ✓ Algorithm is feasible\n", "\n", " Checking: ClarkAndWrightRR\n", " ✓ Algorithm is feasible\n", "\n", " Checking: ClarkAndWrightSShape\n", " ✓ Algorithm is feasible\n", "\n", " Checking: ClosestDepotMaxSharedArticlesSeedBatching\n", " ✓ Algorithm is feasible\n", "\n", " Checking: ClosestDepotMinDistanceSeedBatching\n", " ✓ Algorithm is feasible\n", "\n", " Checking: CombinedBatchingRoutingAssigning\n", " ✓ Algorithm is feasible\n", "\n", " Checking: DueDate\n", " ✗ orders: constraint violated - due_date=False does not satisfy {'equals': True}\n", "\n", " Checking: FiFo\n", " ✗ orders: constraint violated - order_date=False does not satisfy {'equals': True}\n", "\n", " Checking: GreedyIA\n", " ✓ Algorithm is feasible\n", "\n", " Checking: NNItemAssignment\n", " ✗ storage: type 'dedicated' not in required types ['scattered']\n", "\n", " Checking: LargestGap\n", " ✓ Algorithm is feasible\n", "\n", " Checking: LSBatchingNNDueDate\n", " ✗ orders: constraint violated - due_date=False does not satisfy {'equals': True}\n", "\n", " Checking: LSBatchingNNFiFo\n", " ✗ orders: constraint violated - order_date=False does not satisfy {'equals': True}\n", "\n", " Checking: LSBatchingNNRand\n", " ✓ Algorithm is feasible\n", "\n", " Checking: LSBatchingRR\n", " ✓ Algorithm is feasible\n", "\n", " Checking: Midpoint\n", " ✓ Algorithm is feasible\n", "\n", " Checking: NearestNeighbourhood\n", " ✓ Algorithm is feasible\n", "\n", " Checking: OrderNrFiFo\n", " ✗ orders: missing required features ['order_number']\n", "\n", " Checking: DummyOS\n", " ✓ Algorithm is feasible\n", "\n", " Checking: GreedyOS\n", " ✗ warehouse_info: type 'offline' not in required types ['online']\n", "\n", " Checking: MinSharedAislesOS\n", " ✗ warehouse_info: type 'offline' not in required types ['online']\n", "\n", " Checking: MinMaxAislesOS\n", " ✗ resources: type 'human' not in required types ['mixed']\n", "\n", " Checking: MinMaxArticlesOS\n", " ✗ resources: type 'human' not in required types ['mixed']\n", "\n", " Checking: PLRouting\n", " ✓ Algorithm is feasible\n", "\n", " Checking: Random\n", " ✓ Algorithm is feasible\n", "\n", " Checking: RandomMinDistanceSeedBatching\n", " ✓ Algorithm is feasible\n", "\n", " Checking: RandomSimArticlesSeedBatching\n", " ✗ orders: missing required features ['id']\n", "\n", " Checking: Return\n", " ✓ Algorithm is feasible\n", "\n", " Checking: RatliffRosenthal\n", " ✓ Algorithm is feasible\n", "\n", " Checking: SPRPNF\n", " ✗ layout: missing required features ['state_graph']\n", "\n", " Checking: SShape\n", " ✓ Algorithm is feasible\n", "\n", " Checking: ExactSolving\n", " ✓ Algorithm is feasible\n", "\n", "Requirement filtering: 20/32 algorithms feasible\n", "\n", "Final result: 20/38 algorithms are feasible\n" ] } ], "execution_count": 13 }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.530836Z", "start_time": "2026-01-19T15:16:30.525755Z" } }, "cell_type": "code", "source": "subset", "id": "9500b912ad172c86", "outputs": [ { "data": { "text/plain": [ "[name=ClarkAndWrightNN problem=batching>,\n", " name=ClarkAndWrightRR problem=batching>,\n", " name=ClarkAndWrightSShape problem=batching>,\n", " name=ClosestDepotMaxSharedArticlesSeedBatching problem=batching>,\n", " name=ClosestDepotMinDistanceSeedBatching problem=batching>,\n", " name=CombinedBatchingRoutingAssigning problem=batching>,\n", " name=GreedyIA problem=item_assignment>,\n", " name=LargestGap problem=routing>,\n", " name=LSBatchingNNRand problem=batching>,\n", " name=LSBatchingRR problem=batching>,\n", " name=Midpoint problem=routing>,\n", " name=NearestNeighbourhood problem=routing>,\n", " name=DummyOS problem=order_selection>,\n", " name=PLRouting problem=routing>,\n", " name=Random problem=batching>,\n", " name=RandomMinDistanceSeedBatching problem=batching>,\n", " name=Return problem=routing>,\n", " name=RatliffRosenthal problem=routing>,\n", " name=SShape problem=routing>,\n", " name=ExactSolving problem=routing>]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "execution_count": 14 }, { "metadata": {}, "cell_type": "markdown", "source": [ "Complete Example\n", "================" ], "id": "d0f7d9851ede3637" }, { "metadata": { "ExecuteTime": { "end_time": "2026-01-19T15:16:30.606349Z", "start_time": "2026-01-19T15:16:30.530836Z" } }, "cell_type": "code", "source": [ "from ware_ops_algos.utils.visualization import plot_route\n", "from ware_ops_algos.algorithms import ExactTSPRoutingDistance, NearestNeighbourhoodRouting, SShapeRouting, \\\n", " OrderNrFifoBatching, GreedyItemAssignment\n", "\n", "orders = domain_instance.orders\n", "layout = domain_instance.layout\n", "resources = domain_instance.resources\n", "articles = domain_instance.articles\n", "storage_locations = domain_instance.storage\n", "\n", "layout_network = layout.layout_network\n", "graph_data = layout.graph_data\n", "graph = layout_network.graph\n", "graph_params = layout.graph_data\n", "dima = layout_network.distance_matrix\n", "\n", "selector = GreedyItemAssignment(storage_locations)\n", "ia_sol = selector.solve(orders.orders)\n", "orders.orders = ia_sol.resolved_orders\n", "\n", "batcher = OrderNrFifoBatching(\n", " pick_cart=resources.resources[0].pick_cart,\n", " articles=articles\n", " )\n", "\n", "batching_sol = batcher.solve(orders.orders)\n", "\n", "print(batching_sol.execution_time)\n", "\n", "# Build pick list from batches\n", "batches = batching_sol.batches\n", "pick_lists = []\n", "for batch in batches:\n", " pick_list = []\n", " for order in batch.orders:\n", " for pos in order.pick_positions:\n", " pick_list.append(pos)\n", " pick_lists.append(pick_list)\n", "\n", "ss_router = SShapeRouting(\n", " start_node=layout_network.start_node,\n", " end_node=layout_network.end_node,\n", " closest_node_to_start=layout_network.closest_node_to_start,\n", " min_aisle_position=layout_network.min_aisle_position,\n", " max_aisle_position=layout_network.max_aisle_position,\n", " distance_matrix=layout_network.distance_matrix,\n", " predecessor_matrix=layout_network.predecessor_matrix,\n", " picker=resources.resources,\n", " gen_tour=True,\n", " gen_item_sequence=True,\n", " node_list=layout_network.node_list,\n", " node_to_idx={node: idx for idx, node in enumerate(list(layout_network.graph.nodes))},\n", " idx_to_node={idx: node for idx, node in enumerate(list(layout_network.graph.nodes))}\n", " )\n", "\n", "total_dist = 0\n", "for pl in pick_lists:\n", " sol = ss_router.solve(pl)\n", " total_dist += sol.route.distance\n", " plot_route(graph, sol.route.route)\n", " ss_router.reset_parameters()\n", "print(total_dist)" ], "id": "6043ea2e5b04a3e", "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2.7099973522126675e-05\n" ] }, { "data": { "text/plain": [ "
" ], "image/png": "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" }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "26.0\n" ] } ], "execution_count": 15 } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }