File size: 3,247 Bytes
08e15f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from solverforge_legacy.solver.score import (
    ConstraintFactory,
    HardSoftScore,
    constraint_provider,
)

from .domain import Vehicle, Visit

VEHICLE_CAPACITY = "vehicleCapacity"
MINIMIZE_TRAVEL_TIME = "minimizeTravelTime"
SERVICE_FINISHED_AFTER_MAX_END_TIME = "serviceFinishedAfterMaxEndTime"
MAX_ROUTE_DURATION = "maxRouteDuration"


@constraint_provider
def define_constraints(factory: ConstraintFactory):
    return [
        # Hard constraints
        vehicle_capacity(factory),
        service_finished_after_max_end_time(factory),
        # max_route_duration(factory),  # Optional extension - disabled by default
        # Soft constraints
        minimize_travel_time(factory),
    ]


##############################################
# Hard constraints
##############################################


def vehicle_capacity(factory: ConstraintFactory):
    return (
        factory.for_each(Vehicle)
        .filter(lambda vehicle: vehicle.calculate_total_demand() > vehicle.capacity)
        .penalize(
            HardSoftScore.ONE_HARD,
            lambda vehicle: vehicle.calculate_total_demand() - vehicle.capacity,
        )
        .as_constraint(VEHICLE_CAPACITY)
    )


def service_finished_after_max_end_time(factory: ConstraintFactory):
    return (
        factory.for_each(Visit)
        .filter(lambda visit: visit.is_service_finished_after_max_end_time())
        .penalize(
            HardSoftScore.ONE_HARD,
            lambda visit: visit.service_finished_delay_in_minutes(),
        )
        .as_constraint(SERVICE_FINISHED_AFTER_MAX_END_TIME)
    )


##############################################
# Soft constraints
##############################################


def minimize_travel_time(factory: ConstraintFactory):
    return (
        factory.for_each(Vehicle)
        .penalize(
            HardSoftScore.ONE_SOFT,
            lambda vehicle: vehicle.calculate_total_driving_time_seconds(),
        )
        .as_constraint(MINIMIZE_TRAVEL_TIME)
    )


##############################################
# Optional constraints (disabled by default)
##############################################


def max_route_duration(factory: ConstraintFactory):
    """
    Hard constraint: Vehicle routes cannot exceed 8 hours total duration.

    The limit of 8 hours is chosen based on typical driver shift limits:
    - PHILADELPHIA: 55 visits across 6 vehicles, routes typically 4-6 hours
    - FIRENZE: 77 visits across 6 vehicles, routes can approach 8 hours

    Note: A limit that's too low may make the problem infeasible.
    Always ensure your constraints are compatible with your data dimensions.
    """
    MAX_DURATION_SECONDS = 8 * 60 * 60  # 8 hours

    return (
        factory.for_each(Vehicle)
        .filter(lambda vehicle: len(vehicle.visits) > 0)
        .filter(lambda vehicle:
            (vehicle.arrival_time - vehicle.departure_time).total_seconds()
            > MAX_DURATION_SECONDS
        )
        .penalize(
            HardSoftScore.ONE_HARD,
            lambda vehicle: int(
                ((vehicle.arrival_time - vehicle.departure_time).total_seconds()
                 - MAX_DURATION_SECONDS) / 60
            ),
        )
        .as_constraint(MAX_ROUTE_DURATION)
    )