File size: 7,427 Bytes
f3fc7bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
"""
heuristics.py β€” Dispatch Heuristics for Warehouse Job Shop Scheduling

Provides six industry-standard dispatch rules plus stub wrappers for
ML-driven hybrid dispatch (filled in by hybrid_scheduler.py).

Academic References
-------------------
- FIFO (First-In First-Out):
    Standard queue discipline; no specific citation needed.

- Priority-EDD (Earliest Due Date):
    Jackson, J.R. (1955). Scheduling a production line to minimize
    maximum tardiness. Management Research Project Report 43, UCLA.

- Critical Ratio (CR):
    Conway, R.W., Maxwell, W.L., & Miller, L.W. (1967). Theory of
    Scheduling. Addison-Wesley.
    Also: Pinedo, M.L. (2016). Scheduling: Theory, Algorithms, and
    Systems. Springer (5th ed.). doi:10.1007/978-3-319-26580-3.

- ATC (Apparent Tardiness Cost):
    Vepsalainen, A.P.J. & Morton, T.E. (1987). Priority rules for job
    shops with weighted tardiness costs. Management Science, 33(8),
    1035-1047. doi:10.1287/mnsc.33.8.1035.

- WSPT (Weighted Shortest Processing Time):
    Smith, W.E. (1956). Various optimizers for single-stage production.
    Naval Research Logistics Quarterly, 3(1-2), 59-66.
    doi:10.1002/nav.3800030106. [Optimal for weighted completion time.]

- Slack (Minimum Slack):
    Pinedo, M.L. (2016). Scheduling: Theory, Algorithms, and Systems.
    Springer (5th ed.). doi:10.1007/978-3-319-26580-3.

Hyper-heuristic framework (ML selection over these 6 rules):
    Burke, E.K. et al. (2013). Hyper-heuristics: A survey of the state
    of the art. JORS, 64(12), 1695-1724. doi:10.1057/jors.2013.71.
    Cowling, P., Kendall, G., & Soubeiga, E. (2001). A hyperheuristic
    approach to scheduling a sales summit. PATAT 2000, LNCS 2079.
"""

from __future__ import annotations

import math
import logging
from typing import Any, Dict, List

logger = logging.getLogger(__name__)

# Priority class mapping (higher number = higher priority in dispatch)
_PRIORITY_CLASS: Dict[str, int] = {
    "E": 4,  # Express β€” highest
    "A": 3,
    "C": 2,
    "B": 1,
    "D": 0,  # Deferred β€” lowest
}


def get_priority_class(job_type: str) -> int:
    """Return numeric priority class for a job type string."""
    return _PRIORITY_CLASS.get(job_type, 1)


def compute_critical_ratio(job: Any, current_time: float) -> float:
    """Compute the Critical Ratio for a job.

    CR = time_remaining_to_due / remaining_processing_time

    A CR < 1 means the job is behind schedule. Negative CR means already late.
    CR = 999.0 is returned when remaining_proc = 0 (done job β€” large finite value).
    """
    time_to_due = job.due_date - current_time
    remaining_proc = job.remaining_proc_time()

    if remaining_proc <= 0:
        return 999.0  # done job β€” large finite value, sorts last in ascending CR dispatch
    if time_to_due <= 0:
        return time_to_due / remaining_proc  # negative CR = already late

    return time_to_due / remaining_proc


# ---------------------------------------------------------------------------
# Baseline heuristics
# ---------------------------------------------------------------------------

# Ref: Standard queue discipline β€” no specific academic citation required.
def fifo_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """First-In First-Out dispatch: sort by arrival_time ascending."""
    return sorted(jobs, key=lambda j: j.arrival_time)


# Ref: Jackson (1955), "Scheduling a production line to minimize maximum tardiness",
#      Management Research Project Report 43, UCLA.
# Extended with priority classes for multi-tier fulfillment environments.
def priority_edd_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """Priority-EDD dispatch: sort by (priority_class DESC, due_date ASC)."""
    return sorted(
        jobs,
        key=lambda j: (-get_priority_class(j.job_type), j.due_date),
    )


# Ref: Conway et al. (1967), "Theory of Scheduling", Addison-Wesley.
# Also: Pinedo (2016), "Scheduling: Theory, Algorithms, and Systems", Springer 5th ed.
def critical_ratio_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """Critical Ratio dispatch: sort by CR ascending (most urgent first)."""
    return sorted(jobs, key=lambda j: compute_critical_ratio(j, current_time))


# Priority weight mapping (mirrors simulator definitions)
_PRIORITY_WEIGHT: Dict[str, float] = {
    "A": 2.0, "B": 1.5, "C": 1.0, "D": 0.8, "E": 3.0,
}


# Ref: Vepsalainen, A.P.J. & Morton, T.E. (1987). Priority rules for job shops
#      with weighted tardiness costs. Management Science, 33(8), 1035-1047.
#      doi:10.1287/mnsc.33.8.1035
def atc_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """Apparent Tardiness Cost (ATC) dispatch.

    ATC_i = (w_i / p_i) * exp(-max(0, d_i - p_i - t) / (K * p_avg))

    where K is the look-ahead parameter (K=2.0), p_avg is the average
    remaining processing time across waiting jobs.
    Higher ATC score β†’ dispatch sooner.

    Reference: Vepsalainen & Morton (1987), Management Science 33(8):1035-1047.
    """
    if not jobs:
        return jobs

    p_vals = [max(j.remaining_proc_time(), 0.001) for j in jobs]
    p_avg = sum(p_vals) / len(p_vals)
    K = 2.0  # look-ahead parameter

    def _atc_score(job: Any) -> float:
        w = _PRIORITY_WEIGHT.get(job.job_type, 1.0)
        p = max(job.remaining_proc_time(), 0.001)
        slack = job.due_date - p - current_time
        urgency = math.exp(-max(0.0, slack) / max(K * p_avg, 0.001))
        return (w / p) * urgency

    return sorted(jobs, key=_atc_score, reverse=True)


# Ref: Smith, W.E. (1956). Various optimizers for single-stage production.
#      Naval Research Logistics Quarterly, 3(1-2), 59-66.
#      doi:10.1002/nav.3800030106
#      [Proven optimal for minimizing weighted completion time on a single machine.]
def wspt_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """Weighted Shortest Processing Time (WSPT) dispatch.

    Sort by w_i / p_i descending β€” prioritizes jobs with high
    priority-to-processing-time ratio.

    Reference: Smith (1956), Naval Research Logistics Quarterly 3(1-2):59-66.
    """
    def _wspt_score(job: Any) -> float:
        w = _PRIORITY_WEIGHT.get(job.job_type, 1.0)
        p = max(job.remaining_proc_time(), 0.001)
        return w / p

    return sorted(jobs, key=_wspt_score, reverse=True)


# Ref: Pinedo, M.L. (2016). Scheduling: Theory, Algorithms, and Systems.
#      Springer, 5th edition. doi:10.1007/978-3-319-26580-3.
def slack_dispatch(jobs: List[Any], current_time: float, zone_id: int) -> List[Any]:
    """Slack-based dispatch: sort by remaining slack ascending.

    Slack = (due_date - current_time) - remaining_proc_time
    Lower slack β†’ less margin β†’ dispatch sooner.

    Reference: Pinedo (2016), Scheduling: Theory, Algorithms, and Systems.
    """
    def _slack(job: Any) -> float:
        return (job.due_date - current_time) - job.remaining_proc_time()

    return sorted(jobs, key=_slack)


# Dispatch map for convenience
DISPATCH_MAP = {
    "fifo": fifo_dispatch,
    "priority_edd": priority_edd_dispatch,
    "critical_ratio": critical_ratio_dispatch,
    "atc": atc_dispatch,
    "wspt": wspt_dispatch,
    "slack": slack_dispatch,
}

ALL_HEURISTICS = list(DISPATCH_MAP.keys())
HEURISTIC_LABELS = ["FIFO", "Priority-EDD", "Critical-Ratio", "ATC", "WSPT", "Slack"]