[Python] Restriction of traversal through specific aisles to "enter-exit from same side" in a...

Discussão em 'Python' iniciado por Stack, Abril 24, 2025.

  1. Stack

    Stack Membro Participativo

    I'm working on a warehouse layout in Python where a route is calculated between multiple pick locations. The layout includes both wide and 'ultra-narrow' aisles. In the real-world setup, ultra-narrow aisles require that pickers leave their carts at the entrance and return the same way — meaning they must enter and exit from the same side (either the top or bottom). Only at designated cross-aisles can pickers fully traverse through.

    I’m using a 2D walkable area approach and generating a networkx graph over allowed points (at 0.5m resolution), and computing the shortest path between pick locations by using nx.shortest_path.

    What I’d like to do:

    • For specified ultra-narrow aisles, ensure that if the path enters the aisle from one side (say the top), it also exits from the same side.
    • Allow full traversal (in-out from either side) only at cross-aisles.

    Is there an effective way to constrain traversal behavior in networkx or the graph itself to enforce this kind of "U-turn only" constraint for certain regions?

    EDIT: Here is a simplified version of my code. Given the four pick locations, I want the route to access aisle 112 from the bottom to visit P1, then go to P2 in aisle 113. After that, it should leave the aisles from the bottom to then visit aisle 114 to visit P3 and P4 respectively.

    # -*- coding: utf-8 -*-

    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib.patches import Rectangle
    import networkx as nx


    # ------------------- Define warehouse -------------------
    # Visual X positions for vertical shelf aisles
    visual_positions = {
    111: -5.4,
    112: -4.8,
    113: -3.2,
    114: -2.6,
    }

    # Aisles that only allow access from one side
    shelf_access_side = {
    111: "left",
    112: "right",
    113: "left",
    114: "right"
    }

    # Generate Y-pick positions for vertical aisles
    pick_locations = {
    111: [round(2.9 + i * (1.3 / 3), 3) for i in range(48)],
    112: [round(2.9 + i * (1.3 / 3), 3) for i in range(48)],
    113: [round(2.9 + i * (1.3 / 3), 3) for i in range(48)],
    114: [round(2.9 + i * (1.3 / 3), 3) for i in range(48)],
    }

    # Walkable zones (open spaces + narrow aisle access)
    walkable_areas = [
    ((-7, 0), (0, 2.9)), # bottom walkway
    ((-7, 23.8), (-2, 24.8)), # top walkway
    ((-7, 0), (-5.4, 24)), # left cross-aisle
    ((-2, 0), (0, 24.8)) # right cross-aisle
    ]

    x_positions = sorted([visual_positions[a] for a in shelf_access_side])
    for i in range(len(x_positions) - 1):
    left, right = x_positions, x_positions[i+1]
    if right - (left + 0.6) >= 0.5:
    x_start = round(round((left + 0.6) * 2) / 2, 2)
    x_end = round(round((right) * 2) / 2, 2)
    if x_start < (left+0.6):
    x_start += 0.5
    if x_end > right:
    x_end -= 0.5

    y_start, y_end = 2.5, 24
    walkable_areas.append(((x_start, y_start), (x_end, y_end)))


    # ------------------- Input Picks -------------------
    user_picks = ['112.6', '113.33', '114.43', '114.16']

    def parse_user_picks(user_picks):
    parsed = []
    for p in user_picks:
    aisle_str, loc_str = p.split(".")
    aisle = int(aisle_str)
    location = int(loc_str)
    pick_slot_height = round(1.3 / 3, 3) # ~0.433m
    pick_location = round(2.9 + (location - 1) * pick_slot_height, 3)
    parsed.append((aisle, pick_location))
    return parsed

    parsed_picks = parse_user_picks(user_picks)

    df = pd.DataFrame(list(parsed_picks), columns=['Aisle', 'Pick Location'])


    # ------------------- Plotting Functions -------------------
    def plot_pick_locations(ax, pick_locations, visual_positions, aisles):
    for aisle in aisles:
    pos = visual_positions[aisle]
    for location in pick_locations[aisle]:
    rect = Rectangle((pos, location), 0.6, round(1.3 / 3, 3), color='blue', alpha=0.3)
    ax.add_patch(rect)

    def plot_selected_picks(ax, df, visual_positions):
    for _, row in df.iterrows():
    pos = visual_positions[row['Aisle']]
    rect = Rectangle((pos, row['Pick Location']), 0.6, round(1.3 / 3, 3), color='red')
    ax.add_patch(rect)

    def plot_walkable_areas(ax, walkable_areas):
    for (x_start, y_start), (x_end, y_end) in walkable_areas:
    width = x_end - x_start
    height = y_end - y_start
    ax.add_patch(Rectangle((x_start, y_start), width, height, color='lightgrey'))


    # ------------------- Graphing Functions -------------------
    # Build walkable graph
    def generate_walkable_graph(walkable_areas, resolution=0.5):
    walkable_points = set()
    for (x_start, y_start), (x_end, y_end) in walkable_areas:
    x = x_start
    while x <= x_end:
    y = y_start
    while y <= y_end:
    point = (round(x, 3), round(y, 3))
    walkable_points.add(point)
    y += resolution
    x += resolution
    G = nx.Graph()
    directions = [(resolution, 0), (-resolution, 0), (0, resolution), (0, -resolution)]
    for x, y in walkable_points:
    for dx, dy in directions:
    neighbor = (round(x + dx, 3), round(y + dy, 3))
    if neighbor in walkable_points:
    G.add_edge((x, y), neighbor, weight=resolution)
    return G, walkable_points

    walkable_graph, walkable_points = generate_walkable_graph(walkable_areas)

    # Helper function to find nearest walkable point
    def nearest_walkable_point(x, y, walkable_points, resolution=0.5):
    rounded_x = round(round(x / resolution) * resolution, 3)
    rounded_y = round(round(y / resolution) * resolution, 3)
    candidate = (rounded_x, rounded_y)
    if candidate in walkable_points:
    return candidate
    min_dist = float('inf')
    nearest = None
    for point in walkable_points:
    dist = (point[0] - x)**2 + (point[1] - y)**2
    if dist < min_dist:
    min_dist = dist
    nearest = point
    return nearest

    def get_visual_coords(pick):
    aisle, location = pick
    if shelf_access_side[aisle] == 'left':
    x = visual_positions[aisle]
    y = location
    else:
    x = visual_positions[aisle] + 0.6
    y = location
    return x, y


    # ------------------- Creating Path -------------------

    # TODO: I want to prevent full traversal through aisles 112–113.
    # For example, if the graph enters aisle 113 from the bottom, it should NOT be able to exit from the top.


    pick_nodes = []
    for aisle, loc in parsed_picks:
    x, y = get_visual_coords((aisle, loc))
    node = nearest_walkable_point(x, y, walkable_points)
    pick_nodes.append(node)


    depot_coords = (-2.0, -2.0)
    depot_node = nearest_walkable_point(*depot_coords, walkable_points)
    stops = [depot_node] + pick_nodes + [depot_node]

    # Build full path
    full_path = []
    for i in range(len(stops) - 1):
    segment = nx.shortest_path(walkable_graph, stops, stops[i+1], weight='weight')
    if full_path:
    full_path += segment[1:]
    else:
    full_path += segment

    # Total length across both segments
    path_length = sum(
    walkable_graph[full_path][full_path[i + 1]]['weight']
    for i in range(len(full_path) - 1)
    )


    # ------------------- Plot -------------------
    plt.figure(figsize=(14, 10))
    ax = plt.gca()

    # Existing layout and picks
    plot_walkable_areas(ax, walkable_areas)
    plot_pick_locations(ax, pick_locations, visual_positions, shelf_access_side)
    plot_selected_picks(ax, df, visual_positions)


    if full_path:
    path_x, path_y = zip(*full_path)
    ax.plot(path_x, path_y, color='red', linewidth=2, label='User Path')

    # Depot
    ax.plot(depot_node[0], depot_node[1], 'ks', markersize=8, label='Depot')

    # User picks
    for node in pick_nodes:
    ax.plot(node[0], node[1], 'o', color='purple', markersize=8)

    for i, node in enumerate(pick_nodes):
    ax.text(node[0] + 0.3, node[1], f"P{i+1}", color='black', fontsize=9)

    plt.xticks(
    [visual_positions[a] for a in visual_positions],
    [f"{a}" for a in visual_positions],
    rotation=45
    )

    ax.set_aspect("equal")
    plt.show()

    Continue reading...

Compartilhe esta Página