order-picking-python / tests /test_constraints.py
blackopsrepl's picture
Upload 31 files
e40294e verified
from solverforge_legacy.solver.test import ConstraintVerifier
from order_picking.domain import (
Product, Order, OrderItem, Trolley, TrolleyStep, OrderPickingSolution
)
from order_picking.warehouse import WarehouseLocation, Side, new_shelving_id, Column, Row
from order_picking.constraints import (
define_constraints,
required_number_of_buckets,
minimize_order_split_by_trolley,
minimize_distance_from_previous_step,
minimize_distance_from_last_step_to_origin,
)
# Test locations
LOCATION_A1_LEFT_5 = WarehouseLocation(
shelving_id=new_shelving_id(Column.COL_A, Row.ROW_1),
side=Side.LEFT,
row=5
)
LOCATION_A1_LEFT_8 = WarehouseLocation(
shelving_id=new_shelving_id(Column.COL_A, Row.ROW_1),
side=Side.LEFT,
row=8
)
LOCATION_B1_LEFT_3 = WarehouseLocation(
shelving_id=new_shelving_id(Column.COL_B, Row.ROW_1),
side=Side.LEFT,
row=3
)
LOCATION_E3_RIGHT_9 = WarehouseLocation(
shelving_id=new_shelving_id(Column.COL_E, Row.ROW_3),
side=Side.RIGHT,
row=9
)
# Bucket capacity for tests (48,000 cm3)
BUCKET_CAPACITY = 48000
constraint_verifier = ConstraintVerifier.build(
define_constraints, OrderPickingSolution, Trolley, TrolleyStep
)
def create_product(id: str, volume: int, location: WarehouseLocation) -> Product:
return Product(id=id, name=f"Product {id}", volume=volume, location=location)
def create_order_with_items(order_id: str, products: list[Product]) -> tuple[Order, list[OrderItem]]:
order = Order(id=order_id, items=[])
items = []
for i, product in enumerate(products):
item = OrderItem(id=f"{order_id}-{i}", order=order, product=product)
order.items.append(item)
items.append(item)
return order, items
def connect(trolley: Trolley, *steps: TrolleyStep):
"""Set up trolley-step relationships."""
trolley.steps = list(steps)
for i, step in enumerate(steps):
step.trolley = trolley
step.previous_step = steps[i - 1] if i > 0 else None
step.next_step = steps[i + 1] if i < len(steps) - 1 else None
class TestRequiredNumberOfBuckets:
def test_not_penalized_when_under_capacity(self):
"""Trolley with enough buckets should not be penalized."""
trolley = Trolley(
id="1",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
product = create_product("p1", 10000, LOCATION_B1_LEFT_3)
order, items = create_order_with_items("o1", [product])
step = TrolleyStep(id="s1", order_item=items[0])
connect(trolley, step)
constraint_verifier.verify_that(required_number_of_buckets).given(
trolley, step
).penalizes_by(0)
def test_penalized_when_over_bucket_count(self):
"""Trolley with too few buckets should be penalized."""
trolley = Trolley(
id="1",
bucket_count=1, # Only 1 bucket
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
# Create order with volume requiring 2 buckets
product1 = create_product("p1", 40000, LOCATION_B1_LEFT_3)
product2 = create_product("p2", 40000, LOCATION_A1_LEFT_8)
order, items = create_order_with_items("o1", [product1, product2])
step1 = TrolleyStep(id="s1", order_item=items[0])
step2 = TrolleyStep(id="s2", order_item=items[1])
connect(trolley, step1, step2)
# Total volume: 80000, bucket capacity: 48000
# Required buckets: ceil(80000/48000) = 2
# Available: 1, excess: 1
constraint_verifier.verify_that(required_number_of_buckets).given(
trolley, step1, step2
).penalizes_by(1)
class TestMinimizeOrderSplitByTrolley:
def test_single_trolley_per_order(self):
"""Order on single trolley should be minimally penalized."""
trolley = Trolley(
id="1",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
product = create_product("p1", 10000, LOCATION_B1_LEFT_3)
order, items = create_order_with_items("o1", [product])
step = TrolleyStep(id="s1", order_item=items[0])
connect(trolley, step)
# 1 trolley * 1000 = 1000
constraint_verifier.verify_that(minimize_order_split_by_trolley).given(
trolley, step
).penalizes_by(1000)
def test_order_split_across_trolleys(self):
"""Order split across trolleys should be penalized more."""
trolley1 = Trolley(
id="1",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
trolley2 = Trolley(
id="2",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
product1 = create_product("p1", 10000, LOCATION_B1_LEFT_3)
product2 = create_product("p2", 10000, LOCATION_A1_LEFT_8)
order, items = create_order_with_items("o1", [product1, product2])
step1 = TrolleyStep(id="s1", order_item=items[0])
step2 = TrolleyStep(id="s2", order_item=items[1])
connect(trolley1, step1)
connect(trolley2, step2)
# 2 trolleys * 1000 = 2000
constraint_verifier.verify_that(minimize_order_split_by_trolley).given(
trolley1, trolley2, step1, step2
).penalizes_by(2000)
class TestMinimizeDistanceFromPreviousStep:
def test_distance_from_trolley_start(self):
"""Distance from trolley start location to first step."""
trolley = Trolley(
id="1",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
product = create_product("p1", 10000, LOCATION_A1_LEFT_8)
order, items = create_order_with_items("o1", [product])
step = TrolleyStep(id="s1", order_item=items[0])
connect(trolley, step)
# Same shelving, same side: |8 - 5| = 3 meters
constraint_verifier.verify_that(minimize_distance_from_previous_step).given(
trolley, step
).penalizes_by(3)
class TestMinimizeDistanceFromLastStepToOrigin:
def test_distance_back_to_origin(self):
"""Distance from last step back to trolley start location."""
trolley = Trolley(
id="1",
bucket_count=4,
bucket_capacity=BUCKET_CAPACITY,
location=LOCATION_A1_LEFT_5
)
product = create_product("p1", 10000, LOCATION_A1_LEFT_8)
order, items = create_order_with_items("o1", [product])
step = TrolleyStep(id="s1", order_item=items[0])
connect(trolley, step)
# Same shelving, same side: |8 - 5| = 3 meters
constraint_verifier.verify_that(minimize_distance_from_last_step_to_origin).given(
trolley, step
).penalizes_by(3)