Dispatching Rules
Tier 1 — stateless rules
shortest_processing_time
Shortest Processing Time at server.
Returns job.routing[server]. Jobs with shorter processing times
at the candidate server are served first.
Reference: Conway, Maxwell & Miller (1967), Theory of Scheduling.
earliest_due_date
Earliest Due Date.
Returns job.due_date. Jobs with earlier due dates are served
first. server is unused (rule is server-agnostic).
operational_due_date
Operational Due Date at server.
Distributes the planned shop-floor slack across the operations of job's routing so each operation gets an interim due date. Defined as
o_ij = t_r + n_ij * max(0, (d_i - t_r) / |R_i|)
where t_r is the job's shop-floor entry time, n_ij is the
static routing-step number of server (1-indexed), d_i is the
due date and |R_i| is the fixed routing length at shop-floor
entry.
For push systems (no PSP), t_r = job.created_at; for pull
systems, t_r = job.psp_exit_at (release time to shop floor).
Reference: Land, Stevenson & Thürer (2014), Integrating load-based order release and priority dispatching, IJPR 52(4), 1059-1073. https://doi.org/10.1080/00207543.2013.836614
Note: this rule assumes each server appears at most once in a job's
routing, so .index() always finds the correct step. This holds in
practice because ProductionJob.routing is a dict keyed by
server, which structurally prevents duplicate entries.
modified_operational_due_date
Modified Operational Due Date at server.
Defined as m_ij = max(o_ij, now + p_ij) where o_ij is the
ODD (see operational_due_date) and p_ij = job.routing[server]. Switches
dynamically between ODD-driven dispatching (slack-timing regime,
when o_ij > now + p_ij) and SPT-driven dispatching (when the
job is late w.r.t. its operational due date and the SPT term
dominates).
Reference: Baker & Kanet (1983), Job shop scheduling with modified due dates, Journal of Operations Management, 4(1), 11-22. https://doi.org/10.1016/0272-6963(83)90022-0
critical_ratio
Critical Ratio at server.
Defined as cr_ij = (d_i - now) / sum(p_ij for j in R_i) — the
ratio of the job's slack time to its remaining processing time.
Lower values (jobs that are running out of slack relative to the
work left) are served first. R_i is the set of operations not
yet completed (i.e. servers not yet exited), including the current
one.
Returns inf if the remaining processing time is zero (defensive;
a queued job always has at least its current operation pending).
Reference: Berry & Rao (1975), Critical Ratio Scheduling: An Experimental Analysis, Management Science, 22(2), 192-201. https://doi.org/10.1287/mnsc.22.2.192
first_come_first_served
First Come First Served.
Returns 0.0 for every job, so the SimPy key tuple's time
component (entry timestamp) does the tiebreaking. Equivalent to
the Router's no-rule default but explicit at the call site.
work_in_next_queue
Work In Next Queue (WINQ).
Returns the total processing time of the jobs waiting in the queue of the next machine on job's routing. Jobs whose next machine has less queued work are served first, feeding soon-to-starve downstream machines and adding look-ahead information that SPT lacks.
Queue-only convention: excludes the job currently in service at the next
machine. A job on its last operation has no downstream queue and returns
0.0.
Reference: Blackstone, Phillips & Hogg (1982), A state-of-the-art survey of dispatching rules for manufacturing job shop operations, IJPR 20(1), 27-45. https://doi.org/10.1080/00207548208947745
Tier 2 — parameterized rules
planned_slack_time
Build a Planned Slack Time (PST) dispatching rule.
Defined as
pst_ij = (d_i - now) - sum(p_ik + k for k in R_i_from_j)
where R_i_from_j is the set of operations from server through
the end of the routing and k is the per-operation queue-time
allowance. Lower PST = more urgent (the job is closer to being late).
The returned callable yields inf if server is not in the job's
routing or has already been exited — making it safe for priority
comparisons and min() calls.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allowance
|
|
Per-operation queue-time allowance |
0.0
|
Returns:
| Type | Description |
|---|---|
|
A |
|
|
|
|
Raises:
| Type | Description |
|---|---|
|
If |
Reference: Land & Gaalman (1998), The performance of workload control concepts in job shops: Improving the release method, IJPE 56-57, 347-364. https://doi.org/10.1016/S0925-5273(98)00052-8
slack_per_remaining_operation
Build a Slack per Remaining Operation (S/OPN) dispatching rule.
Defined as sopn_ij = pst_ij(k) / |R_i| where pst_ij is the
Planned Slack Time (see planned_slack_time) and |R_i| is
the count of operations not yet completed (servers not yet exited),
including the current one. Lower S/OPN = more urgent.
The returned callable yields inf if server is not in the job's
routing or has already been exited.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allowance
|
|
Per-operation queue-time allowance |
0.0
|
Returns:
| Type | Description |
|---|---|
|
A |
|
|
|
|
Raises:
| Type | Description |
|---|---|
|
If |
Reference: Kanet (1982), Note—On Anomalies in Dynamic Ratio Type Scheduling Rules: A Clarifying Analysis, Management Science, 28(11), 1337-1341. https://doi.org/10.1287/mnsc.28.11.1337
apparent_tardiness_cost
Build an Apparent Tardiness Cost (ATC) dispatching rule.
Priority index (Vepsäläinen & Morton 1987):
I_j = (w_j / p_j) * exp(-max(0, d_j - p_j - t) / (k * p_bar))
where p_j is the imminent-operation processing time, d_j the due
date, t the current time, w_j the job weight, k the look-ahead
(scaling) parameter and p_bar the average processing time of the jobs
queued at the machine. Higher I_j = more urgent; the returned callable
yields -I_j so the lowest key is served first.
The slack uses the imminent operation (d_j - p_j - t), the canonical
single-machine Vepsäläinen-Morton form (not a remaining-work or operational
due-date variant).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lookahead
|
|
Scaling parameter |
required |
avg_processing
|
|
Fixed |
None
|
weight
|
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
|
A |
Raises:
| Type | Description |
|---|---|
|
If |
Reference: Vepsäläinen & Morton (1987), Priority rules for job shops with weighted tardiness costs, Management Science 33(8), 1035-1047. https://doi.org/10.1287/mnsc.33.8.1035
cost_over_time
Build a Cost Over Time (COVERT) dispatching rule.
Priority index:
C_j = w_j * max(0, 1 - max(0, d_j - t - RPT_j) / (k * RPT_j)) / p_j
where RPT_j is the remaining processing time (sum over
unfinished_routing, including the current operation), p_j the
imminent-operation processing time, d_j the due date, t the current
time and k the look-ahead parameter. Higher C_j = more urgent; the
returned callable yields -C_j.
Denominator k * RPT_j is the remaining-work waiting allowance (job-shop
convention; the single-machine variant uses k * p_j). When the job is
tardy or just-in-time (slack <= 0) the rule reduces to a WSPT-like
w_j / p_j; when slack >= k * RPT_j the cost is 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lookahead
|
|
Look-ahead parameter |
required |
weight
|
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
|
A |
Raises:
| Type | Description |
|---|---|
|
If |
Reference: Carroll (1965), Heuristic sequencing of single and multiple component jobs (PhD thesis, MIT). Job-shop form: Russell, Dar-El & Taylor (1987), A comparative analysis of the COVERT job sequencing rule using various shop performance measures, IJPR 25(10), 1523-1540. https://doi.org/10.1080/00207548708919930
raghu_rajendran
Build a Raghu & Rajendran (RR) dispatching rule.
Priority index (Raghu & Rajendran 1993):
Z_j = exp(u) * p_j + (s_j / RPT_j) * exp(-u) * p_j + WINQ_j
where p_j is the imminent-operation processing time, u the current
machine's utilization, s_j = d_j - RPT_j - t the raw slack (may be
negative), RPT_j the remaining processing time (sum over
unfinished_routing) and WINQ_j the work content in the next
machine's queue. RR is a minimum-Z rule, so the index is returned
directly (lowest served first, no negation).
The exponential weighting of the processing-time and due-date terms by the
machine's own utilization is RR's defining feature: the balance differs
machine to machine. A negative s_j (tardy job) lowers Z_j, giving
tardy jobs strong priority.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
utilization
|
|
Fixed machine utilization |
None
|
Returns:
| Type | Description |
|---|---|
|
A |
Raises:
| Type | Description |
|---|---|
|
If |
Reference: Raghu & Rajendran (1993), An efficient dynamic dispatching rule for scheduling in a job shop, IJPE 32(3), 301-313. https://doi.org/10.1016/0925-5273(93)90044-L
Tier 3 — system-state rules
Focus
FOCUS dispatching rule — weighted combination of five impact mechanisms.
The class is stateless beyond its weights. Computations are organised
so each mechanism (pi, omega, psi, gamma, beta)
is exposed independently for testability and for use as a building
block by higher-level policies (DRACO).
All five mechanisms return values in [0, 1] with 1 indicating
a "relevant" impact and 0 an "irrelevant" one. The aggregated
score is the weighted average of the five pieces and also
lies in [0, 1].
Design note: unlike the stateless dispatching-rule factories (e.g.
planned_slack_time), FOCUS is a class because it exposes each
mechanism as an independently testable method and a shared per-decision
build_context consumed by higher-level policies (DRACO). A bare
(job, server) -> float closure cannot expose these. Use
FocusPriorityRule to adapt a Focus to the priority_policy
contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
|
Weight ordering (IMPORTANT). The tuple follows the DRACO
paper's Eq-9 four-mechanism order with The default and the all-equal baseline are order-invariant, but reproducing the Omega paper's per-mechanism ablations requires translating the index — copying the paper's "set wᵢ = 0" verbatim zeroes the wrong mechanism here. To reproduce each ablation (the removed mechanism's weight is 0, the rest 1/4):: The full all-five FOCUS is |
(0.25, 0.25, 0.25, 0.25, 0.0)
|
Example (inside DRACO — one ctx, many candidates): >>> focus = Focus(weights=(0.2, 0.2, 0.2, 0.2, 0.2)) >>> ctx = focus.build_context(shopfloor, env.now, psp=psp) >>> for candidate in candidates: ... d_score = focus.score(candidate, server_k, ctx, env.now)
FocusContext
Snapshot of shop-wide aggregates at a single decision instant.
Built once per decision via Focus.build_context and reused
across all candidates being scored at that instant. Computing this
object is O(|O| · |J|) (the |J| factor comes from the beta
entropy pass; without beta the cost is O(|O|)).
Attributes:
| Name | Type | Description |
|---|---|---|
|
|
Max processing time over all pending |
|
|
Servers whose |
|
|
Max of |
|
|
Max of |
|
|
Per-server workload |
|
|
Read-only mapping |
|
|
Shop-wide workload entropy at the snapshot instant
( |
|
|
Max of |
|
|
Read-only mapping |
FocusPriorityRule
Adapter exposing Focus as a simulatte priority_policy.
Wraps a Focus and a ShopFloor
into a (job, server) -> float callable suitable for
simulatte.router.Router.priority_policies. The returned value
is the negated FOCUS score because simulatte's
simpy.resources.resource.PriorityResource sorts ascending
(lower key = served first).
Liveness guarantee
simulatte.server.Server.sort_queue re-evaluates
priority_policy for every queued request before every
dispatch event (auto-called by _trigger_put). The context
is memoized within a single sort_queue pass (same now
and frozen shop state → one build reused across all requests in
that pass) and rebuilt whenever the scanned shop state changes,
so the key returned at dispatch time always reflects current
shop aggregates — no external refresh helper is needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
focus
|
|
A |
required |
shopfloor
|
|
The shopfloor against which |
required |
psp
|
|
Optional PreShopPool; when provided its jobs are included
in the |
None
|