DAHS / src /heuristics.py
Vittal-M's picture
Upload 41 files
2850928 verified
"""
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"]