File size: 1,918 Bytes
e40294e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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 [
        # Hard constraints
        required_number_of_buckets(factory),
        # Soft constraints
        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)
    )