| | from solverforge_legacy.solver.score import ( |
| | constraint_provider, |
| | ConstraintFactory, |
| | HardSoftDecimalScore, |
| | ) |
| |
|
| | from .domain import Trolley |
| |
|
| |
|
| | REQUIRED_NUMBER_OF_BUCKETS = "Required number of buckets" |
| | MINIMIZE_ORDER_SPLIT = "Minimize order split by trolley" |
| | MINIMIZE_DISTANCE = "Minimize total distance" |
| |
|
| |
|
| | @constraint_provider |
| | def define_constraints(factory: ConstraintFactory): |
| | return [ |
| | |
| | required_number_of_buckets(factory), |
| | |
| | minimize_order_split_by_trolley(factory), |
| | minimize_total_distance(factory), |
| | ] |
| |
|
| |
|
| | def required_number_of_buckets(factory: ConstraintFactory): |
| | """ |
| | Hard: Ensure trolley has enough buckets for all orders. |
| | """ |
| | return ( |
| | factory.for_each(Trolley) |
| | .filter(lambda trolley: trolley.calculate_excess_buckets() > 0) |
| | .penalize( |
| | HardSoftDecimalScore.ONE_HARD, |
| | lambda trolley: trolley.calculate_excess_buckets() |
| | ) |
| | .as_constraint(REQUIRED_NUMBER_OF_BUCKETS) |
| | ) |
| |
|
| |
|
| | def minimize_order_split_by_trolley(factory: ConstraintFactory): |
| | """ |
| | Soft: Orders should ideally be on the same trolley. |
| | """ |
| | return ( |
| | factory.for_each(Trolley) |
| | .filter(lambda trolley: len(trolley.steps) > 0) |
| | .penalize( |
| | HardSoftDecimalScore.ONE_SOFT, |
| | lambda trolley: trolley.calculate_order_split_penalty() |
| | ) |
| | .as_constraint(MINIMIZE_ORDER_SPLIT) |
| | ) |
| |
|
| |
|
| | def minimize_total_distance(factory: ConstraintFactory): |
| | """ |
| | Soft: Minimize total distance traveled by all trolleys. |
| | Aggregated at Trolley level (like vehicle-routing) for performance. |
| | """ |
| | return ( |
| | factory.for_each(Trolley) |
| | .penalize( |
| | HardSoftDecimalScore.ONE_SOFT, |
| | lambda trolley: trolley.calculate_total_distance() |
| | ) |
| | .as_constraint(MINIMIZE_DISTANCE) |
| | ) |
| |
|