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"]
|