Paper
Just-in-Time Scheduling in Two-Stage Flexible Flow Shops
9 pages · 68 marked passages · pdflatex · download PDF · lax-496464
-
The Word RAM
-
Just-in-Time Scheduling in a Two-Stage Flexible Flow Shop
An instance consists of jobs and identical second-stage machines. Job has a preprocessing time , a processing time , a due date and a weight . It is run in two operations, in this order and without preemption: a preprocessing operation of length on the single first-stage machine, and a processing operation of length on one of the identical second-stage machines. Job is completed just in time when its second operation finishes exactly at , and the objective is to maximize the total weight of the just-in-time jobs. In the three-field notation the problem is .
Just-in-time completion pins the second operation of to the interval , where ; the first operation must therefore be finished by , which acts as a deadline for it. Two jobs conflict when those two intervals overlap, and conflicting jobs cannot share a second-stage machine.
Since the objective counts only the just-in-time jobs, a solution is the set of jobs completed just in time, and is feasible when its jobs — and no others — admit a schedule completing every one of them exactly at its due date.
1 import Mathlib.Algebra.Order.BigOperators.Group.Finset 2 import Mathlib.Data.Fintype.BigOperators 3 import Mathlib.Data.Fintype.Powerset 4 … module docstring, 43 lines 48 49 namespace Lax496464.FlowShop 50 51 /-- An instance of the two-stage flexible flow shop: `jobs` jobs, `machines` identical 52 second-stage machines, and for each job a preprocessing time, a processing time, a due 53 date and a weight. -/ 54 structure Instance where 55 /-- The number `n` of jobs. -/ 56 jobs : ℕ 57 /-- The number `m` of identical second-stage machines. -/ 58 machines : ℕ 59 /-- The first-stage (preprocessing) time `p j` of job `j`. -/ 60 p : Fin jobs → ℕ 61 /-- The second-stage (processing) time `q j` of job `j`. -/ 62 q : Fin jobs → ℕ 63 /-- The due date `d j` of job `j`. -/ 64 d : Fin jobs → ℕ 65 /-- The weight `w j` of job `j`. -/ 66 w : Fin jobs → ℕ 67 68 namespace Instance 69 70 variable (I : Instance) 71 72 /-- A job of the instance. -/ 73 abbrev Job : Type := Fin I.jobs 74 75 variable {I} 76 77 /-- `s j = d j - q j`: the time at which job `j`'s second operation must start if `j` is 78 to be completed just in time, and hence a deadline for its first operation. -/ 79 def s (j : I.Job) : ℤ := (I.d j : ℤ) - I.q j 80 81 /-- Jobs `i` and `j` **conflict** when the intervals `[s i, d i)` and `[s j, d j)` of 82 their second operations overlap. Conflicting jobs cannot share a second-stage machine. -/ 83 def Conflict (i j : I.Job) : Prop := s i < (I.d j : ℤ) ∧ s j < (I.d i : ℤ) 84 85 /-- A set of jobs is **independent** when no two of its members conflict, that is, when 86 it can be run on a single second-stage machine. -/ 87 def Independent (Y : Finset I.Job) : Prop := 88 ∀ i ∈ Y, ∀ j ∈ Y, i ≠ j → ¬ Conflict i j 89 90 /-- A **just-in-time schedule of `Z`**: a schedule of the jobs in `Z` completing every 91 one of them exactly at its due date. `pre j` is the start time of `j`'s first operation 92 and `mach j` the second-stage machine running its second operation; the second operation 93 needs no start time, because just-in-time completion pins it to `[s j, d j)`. -/ 94 structure JITSchedule (Z : Finset I.Job) where 95 /-- The start time of the first operation of job `j`. -/ 96 pre : I.Job → ℤ 97 /-- The second-stage machine of job `j`. -/ 98 mach : I.Job → ℕ 99 /-- No operation starts before time `0`. -/ 100 pre_nonneg : ∀ j ∈ Z, 0 ≤ pre j 101 /-- The preprocessing of `j` is finished by the time its second operation must start. -/ 102 pre_le_s : ∀ j ∈ Z, pre j + I.p j ≤ s j 103 /-- The first stage is a single machine: its operations do not overlap. -/ 104 pre_disjoint : ∀ i ∈ Z, ∀ j ∈ Z, i ≠ j → 105 pre i + I.p i ≤ pre j ∨ pre j + I.p j ≤ pre i 106 /-- Only the `m` machines of the instance are used. -/ 107 mach_lt : ∀ j ∈ Z, mach j < I.machines 108 /-- Conflicting jobs do not share a second-stage machine. -/ 109 mach_indep : ∀ i ∈ Z, ∀ j ∈ Z, i ≠ j → mach i = mach j → ¬ Conflict i j 110 111 variable (I) 112 113 /-- `Z` is **feasible**: its jobs can all be completed just in time. -/ 114 def Feasible (Z : Finset I.Job) : Prop := Nonempty (JITSchedule Z) 115 116 /-- The objective value `w(Z) = ∑_{j ∈ Z} w j` of the solution `Z`. -/ 117 def weight (Z : Finset I.Job) : ℕ := ∑ j ∈ Z, I.w j 118 119 /-- The decision version: some feasible set has weight at least `W`. -/ 120 def HasWeight (W : ℕ) : Prop := ∃ Z : Finset I.Job, Feasible I Z ∧ W ≤ weight I Z 121 122 open Classical in 123 /-- The optimum: the largest weight of a feasible set. -/ 124 noncomputable def optimum : ℕ := 125 (Finset.univ.filter fun Z : Finset I.Job => Feasible I Z).sup (weight I) 126 127 /-- The jobs of `Z` whose second operation is running at time `t`. -/ 128 def running (Z : Finset I.Job) (t : ℤ) : Finset I.Job := 129 Z.filter fun i => s i ≤ t ∧ t < (I.d i : ℤ) 130 131 end Instance 132 133 end Lax496464.FlowShop 134 -
Earliest-Start-Time Order, and Distinct Endpoints
Two standing conventions of the paper's algorithmic sections, and the rescaling that establishes the second.
An instance is in earliest-start-time order when its jobs are numbered so that the start times are nondecreasing. Every algorithm of the paper begins by sorting the jobs this way, and its tables are indexed by the resulting order.
The endpoints of an instance are the numbers and . They are distinct when no two of them coincide. Section 4 assumes this, and obtains it by the rescaling
which leaves the weights alone.
1 import Lax496464.FlowShop 2 … module docstring, 34 lines 37 38 namespace Lax496464.EstOrder 39 40 open Lax496464.FlowShop Lax496464.FlowShop.Instance 41 42 /-- The jobs are numbered in nondecreasing order of their start times. -/ 43 def EstOrdered (I : Instance) : Prop := ∀ i j : I.Job, i ≤ j → s i ≤ s j 44 45 /-- No two of the `2n` endpoints `s j` and `d j` coincide. -/ 46 structure DistinctEndpoints (I : Instance) : Prop where 47 /-- Distinct jobs have distinct start times. -/ 48 start_inj : ∀ i j : I.Job, s i = s j → i = j 49 /-- Distinct jobs have distinct due dates. -/ 50 due_inj : ∀ i j : I.Job, (I.d i : ℤ) = (I.d j : ℤ) → i = j 51 /-- No start time is a due date. -/ 52 start_ne_due : ∀ i j : I.Job, s i ≠ (I.d j : ℤ) 53 54 /-- The rescaled instance `d' j = (n+1) d j + j`, `q' j = (n+1) q j`, `p' j = (n+1) p j`, 55 with the weights unchanged. -/ 56 def scale (I : Instance) : Instance where 57 jobs := I.jobs 58 machines := I.machines 59 p j := (I.jobs + 1) * I.p j 60 q j := (I.jobs + 1) * I.q j 61 d j := (I.jobs + 1) * I.d j + (j : ℕ) 62 w j := I.w j 63 64 end Lax496464.EstOrder 65 -
The Two Conditions on a Feasible Set
Two conditions on a set of jobs, one for each stage of the shop.
Condition 1, can be preprocessed in time: for every , the jobs of whose second operations start no later than 's fit, together, into .
Condition 2, can be scheduled on machines: the jobs of can be partitioned into sets, no one of which contains two conflicting jobs.
1 import Lax496464.FlowShop 2 … module docstring, 33 lines 36 37 namespace Lax496464.Conditions 38 39 open Lax496464.FlowShop Lax496464.FlowShop.Instance 40 41 variable (I : Instance) 42 43 /-- **Condition 1.** `Z` can be preprocessed in time: for every `j ∈ Z`, the jobs of `Z` 44 starting no later than `j` fit together into `[0, s j)`. -/ 45 def Preprocessable (Z : Finset I.Job) : Prop := 46 ∀ j ∈ Z, ∑ i ∈ Z.filter (fun i => s i ≤ s j), (I.p i : ℤ) ≤ s j 47 48 /-- **Condition 2.** `Z` can be scheduled on the `m` second-stage machines: its jobs can 49 be coloured with `m` colours so that no two conflicting jobs share a colour. -/ 50 def MSchedulable (Z : Finset I.Job) : Prop := 51 ∃ c : I.Job → ℕ, (∀ j ∈ Z, c j < I.machines) ∧ 52 ∀ i ∈ Z, ∀ j ∈ Z, i ≠ j → c i = c j → ¬ Conflict i j 53 54 end Lax496464.Conditions 55 -
A Set of Jobs Is Feasible Exactly When It Satisfies Both Conditions
A set of jobs can be completed just in time if and only if it satisfies Condition 1 and Condition 2. The two stages therefore decouple: the first-stage machine and the second-stage machines constrain separately, and any pair of schedules meeting the two conditions can be combined into one schedule of the shop.
Condition 2 is in turn a depth condition: fits on second-stage machines exactly when at most of its jobs are running at any one instant. One direction is immediate, since jobs running at a common instant pairwise conflict; the other is the perfectness of interval graphs.
- thm✓
Lax496464.Feasibility(1st statement) - thm✓
Lax496464.Feasibility(2nd statement) - thm✓
Lax496464.Feasibility(3rd statement)
1 import Lax496464.Conditions 2 … module docstring, 39 lines 42 43 namespace Lax496464.Feasibility 44 45 open Lax496464.FlowShop Lax496464.FlowShop.Instance Lax496464.Conditions 46 47 /-- **Section 2.** A set of jobs is feasible exactly when it can be preprocessed in time 48 and can be scheduled on the `m` second-stage machines. -/ 49 axiom feasible_iff (I : Instance) (Z : Finset I.Job) : 50 Feasible I Z ↔ Preprocessable I Z ∧ MSchedulable I Z 51 52 /-- **Condition 2 is a depth condition.** A set of jobs with positive processing times 53 fits on the `m` second-stage machines exactly when at most `m` of them are running at any 54 one instant. -/ 55 axiom mSchedulable_iff_card_running_le (I : Instance) {Z : Finset I.Job} 56 (hq : ∀ j ∈ Z, 0 < I.q j) : 57 MSchedulable I Z ↔ ∀ t : ℤ, (running I Z t).card ≤ I.machines 58 59 /-- **Observation 2.** Feasibility is decided by Condition 1 together with a count, at 60 the start time of each selected job, of the selected jobs running then. -/ 61 axiom feasible_iff_conditions (I : Instance) {Z : Finset I.Job} 62 (hq : ∀ j ∈ Z, 0 < I.q j) : 63 Feasible I Z ↔ 64 Preprocessable I Z ∧ ∀ j ∈ Z, (running I Z (s j)).card ≤ I.machines 65 66 end Lax496464.Feasibility 67 - thm✓
-
no assumptions
Condition 1 is a finite comparison of sums; Condition 2 need only be tested at the start time of each selected job, since the busiest instant of a set of intervals is the left endpoint of one of them.
-
Observation 1
A feasible set of jobs admits a just-in-time schedule whose first stage runs the jobs in nondecreasing order of their start times — earliest start time first. So nothing is lost by looking only at schedules that preprocess in that order, and an algorithm that decides which jobs to select need not also decide in which order to preprocess them.
1 import Lax496464.Conditions 2 … module docstring, 23 lines 26 27 namespace Lax496464.Observation1 28 29 open Lax496464.FlowShop Lax496464.FlowShop.Instance 30 31 /-- **Observation 1.** Every feasible set has a just-in-time schedule that preprocesses 32 in nondecreasing order of start times. -/ 33 axiom exists_est_schedule (I : Instance) (Z : Finset I.Job) (h : Feasible I Z) : 34 ∃ σ : JITSchedule Z, ∀ i ∈ Z, ∀ j ∈ Z, s i < s j → σ.pre i + I.p i ≤ σ.pre j 35 36 end Lax496464.Observation1 37 -
no assumptions
Rebuild the schedule from Condition 1 directly: preprocess the jobs in nondecreasing order of start time, each as early as the previous one allows. Condition 1 is exactly what makes every deadline met.
-
Word Encoding of an Instance
An instance is handed to a word random access machine as a word of numbers: the number of jobs, the number of machines, then the preprocessing times, the processing times, the due dates and the weights, in that order. A decision instance appends the threshold as a final entry.
1 import Lax496464.FlowShop 2 … module docstring, 31 lines 34 35 namespace Lax496464.WordEncoding 36 37 open Lax496464.FlowShop Lax496464.FlowShop.Instance 38 39 /-- The number of jobs declared by a word: its first entry. -/ 40 def jobCount (x : List ℕ) : ℕ := x.getD 0 0 41 42 /-- The number of machines declared by a word: its second entry. -/ 43 def machineCount (x : List ℕ) : ℕ := x.getD 1 0 44 45 /-- The preprocessing time of job `j`, from the block following the two header 46 entries. -/ 47 def preTime (x : List ℕ) (j : ℕ) : ℕ := x.getD (2 + j) 0 48 49 /-- The processing time of job `j`, from the block following the preprocessing times. -/ 50 def procTime (x : List ℕ) (j : ℕ) : ℕ := x.getD (2 + jobCount x + j) 0 51 52 /-- The due date of job `j`, from the block following the processing times. -/ 53 def due (x : List ℕ) (j : ℕ) : ℕ := x.getD (2 + 2 * jobCount x + j) 0 54 55 /-- The weight of job `j`, from the block following the due dates. -/ 56 def wt (x : List ℕ) (j : ℕ) : ℕ := x.getD (2 + 3 * jobCount x + j) 0 57 58 /-- The threshold of a decision instance: the entry following the four blocks. -/ 59 def threshold (x : List ℕ) : ℕ := x.getD (2 + 4 * jobCount x) 0 60 61 /-- The start time `s j = d j - q j` of job `j`, as read off the word. -/ 62 def start (x : List ℕ) (j : ℕ) : ℤ := (due x j : ℤ) - procTime x j 63 64 /-- The word `x` encodes the instance `I`. -/ 65 structure EncodesInstance (x : List ℕ) (I : Instance) : Prop where 66 /-- The word declares `I`'s jobs. -/ 67 jobCount_eq : jobCount x = I.jobs 68 /-- The word declares `I`'s machines. -/ 69 machineCount_eq : machineCount x = I.machines 70 /-- The word is the two header entries followed by four blocks of one number per job. -/ 71 length_eq : x.length = 2 + 4 * I.jobs 72 /-- The preprocessing times are `I`'s. -/ 73 preTime_eq : ∀ j : I.Job, preTime x j = I.p j 74 /-- The processing times are `I`'s. -/ 75 procTime_eq : ∀ j : I.Job, procTime x j = I.q j 76 /-- The due dates are `I`'s. -/ 77 due_eq : ∀ j : I.Job, due x j = I.d j 78 /-- The weights are `I`'s. -/ 79 wt_eq : ∀ j : I.Job, wt x j = I.w j 80 81 /-- The word `x` presents the instance `I` together with the threshold `W`: an instance 82 block followed by the single entry `W`. -/ 83 def EncodesDecisionInstance (x : List ℕ) (I : Instance) (W : ℕ) : Prop := 84 ∃ y, x = y ++ [W] ∧ EncodesInstance y I 85 86 /-- The words that encode an instance, with no threshold. -/ 87 def Instances : Set (List ℕ) := {x | ∃ I, EncodesInstance x I} 88 89 /-- The words that encode a decision instance. -/ 90 def DecisionInstances : Set (List ℕ) := {x | ∃ I W, EncodesDecisionInstance x I W} 91 92 end Lax496464.WordEncoding 93 -
Binary Encoding of an Instance
An instance as a binary word, the representation against which classical complexity measures running time. A number is written as its binary digits preceded by their number in unary, which makes the encoding self-delimiting; an instance is the number of jobs, the number of machines, and then the four arrays of preprocessing times, processing times, due dates and weights, in that order.
1 import Lax496464.FlowShop 2 import Lax434930.PolynomialTime 3 import Mathlib.Data.List.FinRange 4 import Mathlib.Data.Nat.Bits 5 … module docstring, 32 lines 38 39 namespace Lax496464.BinaryEncoding 40 41 open Lax496464.FlowShop Lax496464.FlowShop.Instance Lax434930.PolynomialTime 42 43 /-- A natural number as a binary word: its digits, least significant first, preceded by 44 their number in unary. The unary prefix makes the code self-delimiting. -/ 45 def encodeNat (n : ℕ) : Word := 46 List.replicate n.bits.length true ++ [false] ++ n.bits 47 48 /-- An instance as a binary word: the two counts, then the preprocessing times, the 49 processing times, the due dates and the weights. -/ 50 def encodeInstance (I : Instance) : Word := 51 encodeNat I.jobs ++ encodeNat I.machines ++ 52 (List.finRange I.jobs).flatMap (fun j => encodeNat (I.p j)) ++ 53 (List.finRange I.jobs).flatMap (fun j => encodeNat (I.q j)) ++ 54 (List.finRange I.jobs).flatMap (fun j => encodeNat (I.d j)) ++ 55 (List.finRange I.jobs).flatMap (fun j => encodeNat (I.w j)) 56 57 /-- An instance together with a threshold, as a binary word. -/ 58 def encodeDecisionInstance (I : Instance) (W : ℕ) : Word := 59 encodeInstance I ++ encodeNat W 60 61 /-- The largest number appearing in an instance. A reduction witnesses *strong* 62 NP-hardness when this stays polynomial in the length of its input. -/ 63 def maxNumber (I : Instance) : ℕ := 64 ((List.finRange I.jobs).map fun j => 65 max (max (I.p j) (I.q j)) (max (I.d j) (I.w j))).foldr max (max I.jobs I.machines) 66 67 end Lax496464.BinaryEncoding 68 -
The Just-in-Time Flow Shop as a Problem on Words
The decision problem: given an encoded instance and a threshold , is there a feasible set of jobs of total weight at least ? As a parameterized problem it is taken with the number of machines as its parameter, which is the parameterization of the paper's fourth corollary.
Four further quantities of an instance appear in the running times of the paper's algorithms and are defined here as functions of the word: the threshold itself, the largest processing time , the total preprocessing time , and the width — the largest number of jobs whose second operations are alive at one instant.
1 import Lax496464.ParameterizedComplexity 2 import Mathlib.Data.Nat.Log 3 import Lax496464.WordEncoding 4 … module docstring, 41 lines 46 47 namespace Lax496464.Problems 48 49 open Lax496464.FlowShop Lax496464.FlowShop.Instance 50 open Lax496464.WordEncoding Lax496464.ParameterizedComplexity 51 52 /-- The number of jobs of `x` alive at time `t`, as read off the word. -/ 53 def aliveAt (x : List ℕ) (t : ℤ) : ℕ := 54 ((List.range (jobCount x)).filter 55 fun i => decide (start x i ≤ t ∧ t < (due x i : ℤ))).length 56 57 /-- The **width** `ω` of `x`: the largest number of jobs alive at one instant. -/ 58 def widthOf (x : List ℕ) : ℕ := 59 ((List.range (jobCount x)).map fun j => aliveAt x (start x j)).foldr max 0 60 61 /-- The largest processing time `q_max` declared by `x`. -/ 62 def qmaxOf (x : List ℕ) : ℕ := 63 ((List.range (jobCount x)).map (procTime x)).foldr max 0 64 65 /-- `n log n` on a word of length `n`: the cost of putting the jobs of `x` into 66 earliest-start-time order, which the algorithmic sections of the paper assume has been 67 done. -/ 68 def sortCost (x : List ℕ) : ℕ := (x.length + 1) * (Nat.log 2 (x.length + 2) + 1) 69 70 /-- The total preprocessing time declared by `x`. -/ 71 def preSum (x : List ℕ) : ℕ := ((List.range (jobCount x)).map (preTime x)).sum 72 73 /-- A word is a yes-instance when its instance has a feasible set of weight at least its 74 threshold. -/ 75 def Yes (x : List ℕ) : Prop := 76 ∃ (I : Instance) (W : ℕ), EncodesDecisionInstance x I W ∧ HasWeight I W 77 78 /-- **Just-in-time scheduling in a two-stage flexible flow shop**, as a parameterized 79 problem with the number of machines as its parameter. -/ 80 def byMachines : Problem where 81 Domain := DecisionInstances 82 Yes := Yes 83 param x := machineCount x 84 85 end Lax496464.Problems 86 -
The Dynamic Program of Section 3, and Its Table
The algorithm behind the second theorem. The jobs are in earliest-start-time order and are considered one at a time, from the last to the first. The state carried is a set of at most thresholds, one per second-stage machine: the smallest index a job on that machine may have. The table entry
is the earliest instant at which the first-stage machine may begin, if a set of jobs of total weight compatible with is still to be preprocessed in time. It is when no such set exists, and when the empty set will do.
A set is compatible with when each of its jobs can be assigned a threshold in not exceeding it, with no two conflicting jobs sharing a threshold. Taking to be the first indices asks for nothing beyond schedulability on machines, which is how the table is read off at the end.
The recursion removes the smallest threshold of and decides whether job is selected. If it is not, is replaced by the smallest index above it that is not already a threshold — the paper's — and the table is consulted at the resulting . If it is, the weight drops by and the budget by , and is replaced by the smallest index not already a threshold whose second operation starts at or after — the paper's — giving .
1 import Lax496464.EstOrder 2 import Mathlib.Data.Finset.Max 3 … module docstring, 49 lines 53 54 namespace Lax496464.DynamicProgram 55 56 open Lax496464.FlowShop Lax496464.FlowShop.Instance 57 58 variable (I : Instance) 59 60 /-- `Z` **is compatible with `X`**: every job of `Z` can be given a threshold in `X` not 61 exceeding it, no two conflicting jobs sharing a threshold. -/ 62 def CompatibleWith (X Z : Finset I.Job) : Prop := 63 ∃ mach : I.Job → I.Job, 64 (∀ j ∈ Z, mach j ∈ X) ∧ (∀ j ∈ Z, mach j ≤ j) ∧ 65 ∀ i ∈ Z, ∀ j ∈ Z, i ≠ j → mach i = mach j → ¬ Conflict i j 66 67 /-- `Z` **can be preprocessed from `P`**: starting at the instant `P`, the first-stage 68 machine finishes each job of `Z` by the time its second operation must start. -/ 69 def PreprocessableFrom (Z : Finset I.Job) (P : ℤ) : Prop := 70 ∀ j ∈ Z, P + ∑ i ∈ Z.filter (fun i => i ≤ j), (I.p i : ℤ) ≤ s j 71 72 /-- The table: a set of weight `W'` compatible with `X` can be preprocessed from `P'`. 73 The paper's `T[X, W']` is the largest `P'` for which this holds. -/ 74 def Achievable (X : Finset I.Job) (W' : ℕ) (P' : ℤ) : Prop := 75 ∃ Z : Finset I.Job, weight I Z = W' ∧ CompatibleWith I X Z ∧ PreprocessableFrom I Z P' 76 77 /-- The paper's `j₁`: the smallest index above `j` that is not already a threshold. -/ 78 noncomputable def j1 (X : Finset I.Job) (j : I.Job) : Option I.Job := 79 letI := Classical.decPred fun x : I.Job => x ∉ X ∧ j < x 80 if h : (Finset.univ.filter fun x : I.Job => x ∉ X ∧ j < x).Nonempty then 81 some ((Finset.univ.filter fun x : I.Job => x ∉ X ∧ j < x).min' h) else none 82 83 /-- The paper's `j₂`: the smallest index that is not already a threshold and whose second 84 operation starts at or after `d j`. -/ 85 noncomputable def j2 (X : Finset I.Job) (j : I.Job) : Option I.Job := 86 letI := Classical.decPred fun x : I.Job => x ∉ X ∧ (I.d j : ℤ) ≤ s x 87 if h : (Finset.univ.filter fun x : I.Job => x ∉ X ∧ (I.d j : ℤ) ≤ s x).Nonempty then 88 some ((Finset.univ.filter fun x : I.Job => x ∉ X ∧ (I.d j : ℤ) ≤ s x).min' h) else none 89 90 /-- The paper's `X₁`: the thresholds after `j` is passed over. -/ 91 noncomputable def X1 (X : Finset I.Job) (j : I.Job) : Finset I.Job := 92 match j1 I X j with 93 | some y => insert y (X.erase j) 94 | none => X.erase j 95 96 /-- The paper's `X₂`: the thresholds after `j` is selected. -/ 97 noncomputable def X2 (X : Finset I.Job) (j : I.Job) : Finset I.Job := 98 match j2 I X j with 99 | some y => insert y (X.erase j) 100 | none => X.erase j 101 102 /-- The `m` smallest indices, the thresholds the table is read off at: a set compatible 103 with them is one that fits on `m` machines and nothing more. -/ 104 def firstM : Finset I.Job := Finset.univ.filter fun i : I.Job => (i : ℕ) < I.machines 105 106 end Lax496464.DynamicProgram 107 -
thm✓
Lax496464.Lemma1Lemma 1
Recursion (1) is correct. Let be a set of thresholds whose smallest member is . A set of weight compatible with can be preprocessed from exactly when one of the following holds:
- a set of weight compatible with can be preprocessed from — job is not selected; or
- , the first-stage machine can finish job by when it starts at , and a set of weight compatible with can be preprocessed from — job is selected, and is preprocessed first.
1 import Lax496464.DynamicProgram 2 … module docstring, 35 lines 38 39 namespace Lax496464.Lemma1 40 41 open Lax496464.FlowShop Lax496464.FlowShop.Instance 42 open Lax496464.EstOrder Lax496464.DynamicProgram 43 44 /-- **Lemma 1.** Recursion (1) computes the table. -/ 45 axiom achievable_recursion (I : Instance) (hest : EstOrdered I) (hq : ∀ j : I.Job, 0 < I.q j) 46 {X : Finset I.Job} {j : I.Job} (hjX : j ∈ X) (hjmin : ∀ x ∈ X, j ≤ x) 47 (W' : ℕ) (P' : ℤ) : 48 Achievable I X W' P' ↔ 49 Achievable I (X1 I X j) W' P' ∨ 50 (I.w j ≤ W' ∧ P' + I.p j ≤ s j ∧ 51 Achievable I (X2 I X j) (W' - I.w j) (P' + I.p j)) 52 53 end Lax496464.Lemma1 54 -
no assumptions
thm✓Lax496464.Lemma1Recursion (1), proved in as . The branch in which is passed over needs to admit every set the old thresholds admitted, and the branch in which is taken needs the converse; both are Hall's theorem applied to the map sending a job to the threshold it runs behind.
-
Theorem 2
Just-in-time scheduling in a two-stage flexible flow shop is solved in time, where is the threshold, the number of jobs and the number of second-stage machines: the table of Section 3 has columns and rows, and recursion (1) fills each entry from two earlier ones.
The answer is read off the table at the smallest indices: a set of weight compatible with them and preprocessable from the instant is exactly a feasible solution of weight .
1 import Lax496464.DynamicProgram 2 import Lax496464.Problems 3 … module docstring, 45 lines 49 50 namespace Lax496464.Theorem2 51 52 open Lax496464.FlowShop Lax496464.FlowShop.Instance 53 open Lax496464.EstOrder Lax496464.DynamicProgram 54 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 55 open Lax808846.Ram Lax808846.RamComputes 56 57 /-- **The read-off.** A set of weight `W'` compatible with the `m` smallest indices that 58 can be preprocessed from a nonnegative instant is exactly a feasible set of weight `W'`. -/ 59 axiom achievable_readoff (I : Instance) (hest : EstOrdered I) (W' : ℕ) : 60 (∃ P' : ℤ, 0 ≤ P' ∧ Achievable I (firstM I) W' P') ↔ 61 ∃ Z : Finset I.Job, Feasible I Z ∧ weight I Z = W' 62 63 open Classical in 64 /-- **Theorem 2.** One word RAM program decides the problem within 65 `c · (W+1) · (n+1)^m` instructions, plus the cost of sorting, at every word length 66 admitting the instance and its table. -/ 67 axiom theorem2_time : 68 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 69 ComputesInTime w prog 70 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 71 c * (threshold x + 1) * (jobCount x + 1) ^ machineCount x ≤ 2 ^ w ∧ 72 (∀ j < jobCount x, 0 < procTime x j)} 73 (fun x => if Yes x then [1] else [0]) 74 (fun x => c * (threshold x + 1) * (jobCount x + 1) ^ machineCount x + 75 c * sortCost x) 76 77 end Lax496464.Theorem2 78 -
no assumptions
The smallest indices are thresholds that constrain nothing beyond , so a set compatible with them is one that fits on machines; and being preprocessable from a nonnegative instant is Condition 1.
-
no assumptions
Theorem 2 as a word RAM program. Read the word, sort the jobs by start time, and fill the table of Section 3 over sets of thresholds written as base- numbers: the numbers are visited from the top, each is decided to be a code of a set or not by a recurrence, and the two sets , of recursion (1) are found by one scan of its digits (, independent of ), after which its cells cost each. The sets contribute in all. The table is the monotone one ("weight at least "), so it has rows whatever the weights are, and the answer is one entry of the column of the first indices. The domain restricts to positive processing times, the paper's standing assumption for Lemma 1's recursion. The running time is : no work per machine beyond the scan of a set.
-
Corollary 1
With a single second-stage machine and no weights, the problem is solved in time. It is the program of the second theorem at : the table has columns, the threshold is at most because every weight is one, and the two bounds multiply.
1 import Lax496464.Problems 2 … module docstring, 28 lines 31 32 namespace Lax496464.Corollary1 33 34 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 35 open Lax808846.Ram Lax808846.RamComputes 36 37 open Classical in 38 /-- **Corollary 1.** On one machine with unit weights, the problem is decided within 39 `c · (n+1)²` instructions. -/ 40 axiom corollary1_time : 41 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 42 ComputesInTime w prog 43 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ machineCount x = 1 ∧ 44 (∀ j < jobCount x, wt x j = 1) ∧ (∀ j < jobCount x, 0 < procTime x j)} 45 (fun x => if Yes x then [1] else [0]) 46 (fun x => c * (jobCount x + 1) ^ 2) 47 48 end Lax496464.Corollary1 49 -
no assumptions
The whole program , correct by , compiled and wrapped through the pipeline: decides for every decision word of a one-machine, unit-weight instance with positive processing times that fits, within instructions. The domain restricts to positive processing times — the paper's own standing assumption for Lemma 1's recursion — which the concept's stated domain does not yet carry; see the module notes.
-
Corollary 2
The same table read the other way round solves the problem in time, where is the total preprocessing time: instead of recording, for each weight, the latest instant at which the first stage may begin, record for each instant the largest weight attainable from it. The recursion is the same and so is its correctness; only the axis the table is indexed along changes.
This is the better of the two bounds whenever the preprocessing times are small and the weights are not.
1 import Lax496464.Problems 2 … module docstring, 30 lines 33 34 namespace Lax496464.Corollary2 35 36 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 37 open Lax808846.Ram Lax808846.RamComputes 38 39 open Classical in 40 /-- **Corollary 2.** The dual program decides the problem within `c · (P+1) · (n+1)^m` 41 instructions, plus the cost of sorting. -/ 42 axiom corollary2_time : 43 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 44 ComputesInTime w prog 45 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 46 c * (preSum x + 1) * (jobCount x + 1) ^ machineCount x ≤ 2 ^ w ∧ 47 (∀ j < jobCount x, 0 < procTime x j)} 48 (fun x => if Yes x then [1] else [0]) 49 (fun x => c * (preSum x + 1) * (jobCount x + 1) ^ machineCount x + 50 c * sortCost x) 51 52 end Lax496464.Corollary2 53 -
no assumptions
Corollary 2 as a word RAM program. Read the word, sort the jobs by start time, sum the preprocessing times into , and fill the dual table of Section 3 over sets of thresholds written as base- numbers: the numbers are visited from the top, each is decided to be a code of a set or not by a recurrence, and the two sets , of recursion (1) are found by one scan of its digits (, independent of ), after which its cells cost each. The table records, for each instant , the largest weight attainable from , capped at the threshold (so its entries never leave the word); the block of the empty set is zero, and the answer is the entry of the first indices at instant . The sets contribute in all. The domain restricts to positive processing times, the paper's standing assumption for Lemma 1's recursion. The running time is : no work per machine beyond the scan of a set.
-
Every Instance May Be Assumed to Have Distinct Endpoints
On an instance in earliest-start-time order, the rescaling of Section 4 changes neither which sets of jobs are feasible nor what they weigh, and it makes the endpoints pairwise distinct. So an algorithm may assume distinct endpoints, which is what the sweeps of Sections 4 and 5 do when they step from one endpoint to the next and treat each step as carrying a single event.
- thm✓
Lax496464.Normalization(1st statement) - thm✓
Lax496464.Normalization(2nd statement) - thm✓
Lax496464.Normalization(3rd statement)
1 import Lax496464.Conditions 2 import Lax496464.EstOrder 3 … module docstring, 28 lines 32 33 namespace Lax496464.Normalization 34 35 open Lax496464.FlowShop Lax496464.FlowShop.Instance Lax496464.EstOrder 36 37 /-- **Section 4.** The rescaling preserves feasibility. -/ 38 axiom scale_feasible_iff (I : Instance) (h : EstOrdered I) (hq : ∀ i : I.Job, 0 < I.q i) 39 (Z : Finset I.Job) : 40 Feasible (scale I) Z ↔ Feasible I Z 41 42 /-- The rescaling preserves weights. -/ 43 axiom scale_weight (I : Instance) (Z : Finset I.Job) : 44 weight (scale I) Z = weight I Z 45 46 /-- The rescaling separates the endpoints, and keeps the jobs in earliest-start-time 47 order. -/ 48 axiom scale_distinctEndpoints (I : Instance) (h : EstOrdered I) (hq : ∀ i : I.Job, 0 < I.q i) : 49 DistinctEndpoints (scale I) ∧ EstOrdered (scale I) 50 51 end Lax496464.Normalization 52 - thm✓
-
no assumptions
Scaling multiplies both sides of Condition 1 by and leaves the shift too small to matter, and it preserves the conflict relation, hence Condition 2.
-
The Endpoint Sweep of Section 4, and Its Table
The algorithm behind the first half of the third theorem. The time axis is swept from left to right through the endpoints — the start times and the due dates — and the state carried at an instant is the set of selected jobs alive then, together with the weight selected so far and the preprocessing time spent. The table entry
records whether a feasible selection of weight , all of whose jobs have started by , is alive at in exactly and costs at most to preprocess.
Between two consecutive endpoints nothing happens, and at an endpoint exactly one thing does: a job becomes due, or a job starts. The two recursions of Section 4 say what each does to the table.
Since is a set of jobs alive at one instant, it has at most members, so the table has columns — which is where the running time comes from.
1 import Lax496464.Conditions 2 import Lax496464.EstOrder 3 … module docstring, 38 lines 42 43 namespace Lax496464.Sweep 44 45 open Lax496464.FlowShop Lax496464.FlowShop.Instance 46 47 variable (I : Instance) 48 49 /-- The total preprocessing time of `Z`. -/ 50 def pload (Z : Finset I.Job) : ℤ := ∑ i ∈ Z, (I.p i : ℤ) 51 52 /-- The table: a feasible set of weight `W'`, all of whose jobs have started by `t`, is 53 alive at `t` in exactly `X` and costs at most `P'` to preprocess. -/ 54 def Reachable (t : ℤ) (X : Finset I.Job) (W' : ℕ) (P' : ℤ) : Prop := 55 ∃ Z : Finset I.Job, Feasible I Z ∧ (∀ k ∈ Z, s k ≤ t) ∧ 56 running I Z t = X ∧ weight I Z = W' ∧ pload I Z ≤ P' 57 58 /-- The `2n` endpoints: the start times and the due dates. -/ 59 def endpoints : Finset ℤ := 60 (Finset.univ.image fun k : I.Job => s k) ∪ 61 (Finset.univ.image fun k : I.Job => (I.d k : ℤ)) 62 63 end Lax496464.Sweep 64 -
thm✓
Lax496464.Lemma2Lemma 2
The two recursions of Section 4 are correct.
At an instant at which job becomes due and nothing else happens, the entries at are those at the previous endpoint with either present or absent: is no longer alive, so it has left the state, and whether it was selected is what the two cases record. Equation (4).
At an instant at which job starts and nothing else happens, either is not selected and the state is unchanged, or is selected, in which case it joins the state, the state must still be small enough to fit on the machines, and the first-stage machine must be able to finish by . Equation (3).
Past the last start time the table answers the question: some state carries weight exactly when a feasible set of weight exists.
- thm✓
Lax496464.Lemma2(1st statement) - thm✓
Lax496464.Lemma2(2nd statement) - thm✓
Lax496464.Lemma2(3rd statement)
1 import Lax496464.Sweep 2 … module docstring, 34 lines 37 38 namespace Lax496464.Lemma2 39 40 open Lax496464.FlowShop Lax496464.FlowShop.Instance Lax496464.Sweep 41 42 variable (I : Instance) 43 44 /-- **Equation (4).** The step across an instant at which job `j` becomes due. -/ 45 axiom reachable_due (hq : ∀ i : I.Job, 0 < I.q i) {t' t : ℤ} {j : I.Job} 46 {X : Finset I.Job} {W' : ℕ} {P' : ℤ} 47 (htt : t' < t) (hdj : (I.d j : ℤ) = t) 48 (hnos : ∀ k : I.Job, ¬ (t' < s k ∧ s k ≤ t)) 49 (hnod : ∀ k : I.Job, k ≠ j → ¬ (t' < (I.d k : ℤ) ∧ (I.d k : ℤ) ≤ t)) : 50 Reachable I t X W' P' ↔ 51 j ∉ X ∧ (Reachable I t' X W' P' ∨ Reachable I t' (insert j X) W' P') 52 53 /-- **Equation (3).** The step across an instant at which job `j` starts. -/ 54 axiom reachable_start (hq : ∀ i : I.Job, 0 < I.q i) {t' t : ℤ} {j : I.Job} 55 {X : Finset I.Job} {W' : ℕ} {P' : ℤ} 56 (htt : t' < t) (hsj : s j = t) 57 (hnos : ∀ k : I.Job, k ≠ j → ¬ (t' < s k ∧ s k ≤ t)) 58 (hnod : ∀ k : I.Job, ¬ (t' < (I.d k : ℤ) ∧ (I.d k : ℤ) ≤ t)) : 59 Reachable I t X W' P' ↔ 60 (j ∉ X ∧ Reachable I t' X W' P') ∨ 61 (j ∈ X ∧ (X.erase j).card < I.machines ∧ 62 ∃ (W'' : ℕ) (P'' : ℤ), W'' + I.w j = W' ∧ Reachable I t' (X.erase j) W'' P'' ∧ 63 P'' + I.p j ≤ s j ∧ P'' + I.p j ≤ P') 64 65 /-- **The read-off.** Past the last start time, a state of weight `W'` is exactly a 66 feasible set of weight `W'`. -/ 67 axiom exists_reachable_iff {t : ℤ} (ht : ∀ k : I.Job, s k ≤ t) (W' : ℕ) : 68 (∃ (X : Finset I.Job) (P' : ℤ), Reachable I t X W' P') ↔ 69 ∃ Z : Finset I.Job, Feasible I Z ∧ weight I Z = W' 70 71 end Lax496464.Lemma2 72 - thm✓
-
no assumptions
Past the last start time the alive set and the load carry no further information, so the states of weight are exactly the feasible sets of weight .
-
The Due-Date Profile of Section 5, and Recursion (5)
The algorithm behind the second half of the third theorem. It sweeps the start times in order, and at the start time of job it carries, instead of the set of selected jobs alive then, only their profile: the vector whose -th coordinate counts the selected jobs due at . Since a job's second operation is at most long, only the coordinates can be nonzero, and each is at most ; the table therefore has columns.
Stepping from the previous start time to moves the reference point by , so a profile at becomes a profile at shifted down by . The paper writes for the vectors that shift to , and recursion (5) has the two branches of every such sweep: job is passed over, or job is selected, in which case its own coordinate drops by one.
1 import Lax496464.Sweep 2 import Mathlib.Algebra.BigOperators.Fin 3 import Mathlib.Order.Interval.Finset.Nat 4 … module docstring, 48 lines 53 54 namespace Lax496464.Profile 55 56 open Lax496464.FlowShop Lax496464.FlowShop.Instance Lax496464.Sweep 57 58 variable (I : Instance) 59 60 /-- The **due-date profile** of `Z` at `t`: the number of jobs of `Z` due at `t + i`. -/ 61 def dueProfile (Z : Finset I.Job) (t : ℤ) (i : ℕ) : ℕ := 62 (Z.filter fun k => (I.d k : ℤ) = t + i).card 63 64 /-- The repaired table: a feasible set of weight `W'`, all of whose jobs have started by 65 `t`, has profile exactly `x` at `t` and costs at most `P'` to preprocess. -/ 66 def ReachableProfile (t : ℤ) (x : ℕ → ℕ) (W' : ℕ) (P' : ℤ) : Prop := 67 ∃ Z : Finset I.Job, Feasible I Z ∧ (∀ k ∈ Z, s k ≤ t) ∧ 68 (∀ i, 1 ≤ i → dueProfile I Z t i = x i) ∧ weight I Z = W' ∧ pload I Z ≤ P' 69 70 /-- The start time of the job before `k`, and `0` before the first job. -/ 71 def sBefore (k : I.Job) : ℤ := 72 if h : (k : ℕ) = 0 then 0 else s (⟨(k : ℕ) - 1, by omega⟩ : I.Job) 73 74 /-- The paper's `δⱼ`: how far the reference point moves at job `k`. -/ 75 def delta (k : I.Job) : ℕ := (s k - sBefore I k).toNat 76 77 variable {I} 78 79 /-- The paper's `x⃗^{(q)}`: the vector `x` with coordinate `c` decremented. -/ 80 def decAt {qmax : ℕ} (x : Fin qmax → ℕ) (c : Fin qmax) : Fin qmax → ℕ := 81 Function.update x c (x c - 1) 82 83 variable (I) 84 85 /-- **Recursion (5), exactly as printed.** The entries it derives: the base 86 `T₀[0⃗, 0] = 0`, the convention `Tⱼ[0⃗, 0] = 0`, the capacity test `∑ xᵢ ≤ m` and `W' > 0` 87 on every other entry, and the two branches, each through the printed shift — which 88 constrains the earlier vector only on the coordinates at or above `δ`. -/ 89 inductive Printed (qmax : ℕ) : ℕ → (Fin qmax → ℕ) → ℕ → ℤ → Prop 90 /-- Before the first job, the empty selection. -/ 91 | init : Printed qmax 0 0 0 0 92 /-- At any stage, the empty selection. -/ 93 | zero (t : ℕ) : Printed qmax t 0 0 0 94 /-- Job `k` is passed over. -/ 95 | skip {k : I.Job} {x y : Fin qmax → ℕ} {W : ℕ} {P : ℤ} 96 (hcap : ∑ i, x i ≤ I.machines) (hW : 0 < W) 97 (hshift : ∀ i : Fin qmax, ∀ hi : delta I k ≤ (i : ℕ), 98 y i = x ⟨(i : ℕ) - delta I k, by have := i.isLt; omega⟩) 99 (h : Printed qmax (k : ℕ) y W P) : Printed qmax ((k : ℕ) + 1) x W P 100 /-- Job `k` is selected. -/ 101 | take {k : I.Job} {x y : Fin qmax → ℕ} {W : ℕ} {P : ℤ} (c : Fin qmax) 102 (hc : (c : ℕ) + 1 = I.q k) (hxc : 1 ≤ x c) 103 (hcap : ∑ i, x i ≤ I.machines) (hW : 0 < W) (hwW : I.w k ≤ W) 104 (hshift : ∀ i : Fin qmax, ∀ hi : delta I k ≤ (i : ℕ), 105 y i = decAt x c ⟨(i : ℕ) - delta I k, by have := i.isLt; omega⟩) 106 (h : Printed qmax (k : ℕ) y (W - I.w k) P) (hfit : P + I.p k ≤ s k) : 107 Printed qmax ((k : ℕ) + 1) x W (P + I.p k) 108 109 end Lax496464.Profile 110 -
thm✓
Lax496464.Lemma3Lemma 3
Recursion (5) computes the table of due-date profiles.
Stepping to the start time of job , an entry at is reached either by passing over, in which case some profile at the previous start time shifts to it, or by selecting , in which case the profile with 's own coordinate decremented shifts to it, the weight drops by , and the first-stage machine must be able to finish by . Past the last start time the table answers the question.
The printed form of the recursion is not correct — its shift leaves coordinates unconstrained that the profile of a selection cannot have — and the counterexamples are small: one job on two machines for the branch that selects, two jobs on one machine for the branch that passes over. The algorithm is nonetheless correct as printed. What it maintains is the weaker invariant that an entry is witnessed by a feasible selection of weight and preprocessing cost at most whose profile is at most coordinatewise, and it still derives every feasible selection with its exact profile. A vector that overstates the profile only overstates how many machines are busy, so the capacity test rejects more rather than less, and the optimum read off at the end is exact.
- thm✓
Lax496464.Lemma3(1st statement) - thm✓
Lax496464.Lemma3(2nd statement) - thm✓
Lax496464.Lemma3(3rd statement) - thm✓
Lax496464.Lemma3(4th statement)
1 import Lax496464.Profile 2 … module docstring, 43 lines 46 47 namespace Lax496464.Lemma3 48 49 open Lax496464.FlowShop Lax496464.FlowShop.Instance 50 open Lax496464.Sweep Lax496464.Profile 51 52 variable (I : Instance) 53 54 /-- **Lemma 3, repaired.** The step to the start time of job `j`. -/ 55 axiom reachableProfile_start (hq : ∀ i : I.Job, 0 < I.q i) {qmax : ℕ} 56 (hqmax : ∀ k : I.Job, I.q k ≤ qmax) {t' t : ℤ} {δ : ℕ} {j : I.Job} 57 {x : ℕ → ℕ} {W' : ℕ} {P' : ℤ} 58 (hδ : (δ : ℤ) = t - t') (htt : t' < t) (hsj : s j = t) 59 (hnos : ∀ k : I.Job, k ≠ j → ¬ (t' < s k ∧ s k ≤ t)) : 60 ReachableProfile I t x W' P' ↔ 61 (∃ y : ℕ → ℕ, (∀ i, 1 ≤ i → x i = y (δ + i)) ∧ ReachableProfile I t' y W' P') ∨ 62 (0 < x (I.q j) ∧ (∑ i ∈ Finset.Icc 1 qmax, x i) ≤ I.machines ∧ 63 ∃ (W'' : ℕ) (P'' : ℤ) (y : ℕ → ℕ), 64 W'' + I.w j = W' ∧ 65 (∀ i, 1 ≤ i → (if i = I.q j then x i - 1 else x i) = y (δ + i)) ∧ 66 ReachableProfile I t' y W'' P'' ∧ 67 P'' + I.p j ≤ s j ∧ P'' + I.p j ≤ P') 68 69 /-- **The read-off, repaired.** Past the last start time, a profile of weight `W'` is 70 exactly a feasible set of weight `W'`. -/ 71 axiom exists_reachableProfile_iff {t : ℤ} (ht : ∀ k : I.Job, s k ≤ t) (W' : ℕ) : 72 (∃ (x : ℕ → ℕ) (P' : ℤ), ReachableProfile I t x W' P') ↔ 73 ∃ Z : Finset I.Job, Feasible I Z ∧ weight I Z = W' 74 75 /-- **Lemma 3 is false as printed.** Some instance, with positive processing times and 76 positive weights, has a stage at which the printed recursion derives a profile that no 77 set of jobs has. -/ 78 axiom printed_not_correct : 79 ∃ (J : Instance) (qmax : ℕ) (k : J.Job) (x : Fin qmax → ℕ) (W : ℕ) (P : ℤ), 80 (∀ i : J.Job, 0 < J.q i) ∧ (∀ i : J.Job, 0 < J.w i) ∧ 81 (∀ i : J.Job, J.q i ≤ qmax) ∧ 82 Printed J qmax ((k : ℕ) + 1) x W P ∧ 83 ∀ Z : Finset J.Job, ∃ c : Fin qmax, 84 dueProfile J Z (s k) ((c : ℕ) + 1) ≠ x c 85 86 /-- **The algorithm is correct as printed.** For the recursion exactly as the paper 87 prints it, the weights reached after the last job are exactly the weights of feasible 88 sets. -/ 89 axiom printed_readoff {qmax : ℕ} (hq : ∀ i : I.Job, 0 < I.q i) (hw : ∀ i : I.Job, 0 < I.w i) 90 (hqmax : ∀ i : I.Job, I.q i ≤ qmax) 91 (hdist : ∀ i j : I.Job, i < j → s i < s j) (W : ℕ) : 92 (∃ (x : Fin qmax → ℕ) (P : ℤ), Printed I qmax I.jobs x W P) ↔ 93 ∃ Z : Finset I.Job, Feasible I Z ∧ weight I Z = W 94 95 end Lax496464.Lemma3 96 - thm✓
-
no assumptions
Past the last start time the profile and the load carry no further information.
-
Theorem 3
Two further programs, each fast when one quantity of the instance is small.
The endpoint sweep of Section 4 carries a subset of the jobs alive at the current instant, and so runs in time, where is the largest number of jobs alive at one instant.
The profile sweep of Section 5 carries, instead of that subset, only how many of its jobs are due at each of the next instants, and so runs in time.
Neither bound involves the number of machines in the exponent, so both are useful precisely where the program of the second theorem is not.
1 import Lax496464.Problems 2 … module docstring, 45 lines 48 49 namespace Lax496464.Theorem3 50 51 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 52 open Lax808846.Ram Lax808846.RamComputes 53 54 open Classical in 55 /-- **Theorem 3, the endpoint sweep.** The problem is decided within 56 `c · (W+1) · 2^ω · (n+1)` instructions, plus the cost of sorting. -/ 57 axiom theorem3_width_time : 58 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 59 ComputesInTime w prog 60 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 61 c * (threshold x + 1) * 2 ^ widthOf x * (jobCount x + 1) ≤ 2 ^ w ∧ 62 (∀ j < jobCount x, 0 < procTime x j)} 63 (fun x => if Yes x then [1] else [0]) 64 (fun x => c * (threshold x + 1) * 2 ^ widthOf x * (jobCount x + 1) + 65 c * sortCost x) 66 67 open Classical in 68 /-- **Theorem 3, the profile sweep.** The problem is decided within 69 `c · (W+1) · (m+1)^q_max · (n+1)` instructions, plus the cost of sorting. -/ 70 axiom theorem3_qmax_time : 71 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 72 ComputesInTime w prog 73 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 74 c * (threshold x + 1) * (machineCount x + 1) ^ qmaxOf x * (jobCount x + 1) 75 ≤ 2 ^ w ∧ (∀ j < jobCount x, 0 < procTime x j)} 76 (fun x => if Yes x then [1] else [0]) 77 (fun x => c * (threshold x + 1) * (machineCount x + 1) ^ qmaxOf x * 78 (jobCount x + 1) + c * sortCost x) 79 80 end Lax496464.Theorem3 81 -
no assumptions
The endpoint sweep of Section 4 as a word RAM program: read the word, sort the jobs by start time (), build the arrays of the sorted instance and the order of the due dates, then sweep the events — the start of a job takes a free slot (or a fresh one, doubling the mask range ), the due date frees it — updating a table with one entry for every set of occupied slots and every weight up to , by a flat pass of cells per event. The number of slots is at most the width , so every event costs and the run is within . The domain restricts to positive processing times, the paper's standing assumption for the sweep.
-
no assumptions
The profile sweep of Section 5 as a word RAM program. Read the word, sort the jobs by start time (), compute and a sentinel, then sweep the jobs in that order, carrying for every profile (how many selected jobs are due at each of the next instants, a base number of digits) and every weight the least preprocessing load of a feasible selection of weight at least . One job is one marginalisation pass (the shift of the profile is a division by a power of , the low digits being minimised away) and one take pass, each per table cell, so a job costs and the whole run is within . The answer is read off the profiles at weight . The correctness is that of recursion (5) in its repaired form (); jobs may tie in start time (), no rescaling is needed. The domain restricts to positive processing times, the paper's standing assumption for Lemma 3, exactly as the concept's statement now says.
-
Uniform Preprocessing Times, and Proper Instances
Two restrictions on an instance, both from Section 6 of the paper.
An instance has uniform preprocessing times when all are equal. It is proper when no job's second-operation interval contains another's.
1 import Lax496464.FlowShop 2 … module docstring, 23 lines 26 27 namespace Lax496464.ProperInstances 28 29 open Lax496464.FlowShop Lax496464.FlowShop.Instance 30 31 /-- All preprocessing times are equal. -/ 32 def Uniform (I : Instance) : Prop := ∃ p : ℕ, ∀ j : I.Job, I.p j = p 33 34 /-- No job's second-operation interval contains another's. -/ 35 def Proper (I : Instance) : Prop := 36 ∀ i j : I.Job, i ≠ j → ¬ (s j ≤ s i ∧ (I.d i : ℤ) ≤ (I.d j : ℤ)) 37 38 end Lax496464.ProperInstances 39 -
The Greedy of Section 6.1, and Its Domination Order
The algorithm behind the first half of the fourth theorem, for the case in which all preprocessing times are equal and every weight is one. The jobs are taken in earliest-start-time order, and a set of already selected jobs is maintained. On reaching job :
- if the first stage can still preprocess jobs by , and fewer than of the selected jobs are alive at , then joins ;
- otherwise one job of with the largest due date is dropped, and the rest becomes the new .
The set kept is compared to others through a domination order: dominates when, below every threshold, has no more due dates than .
1 import Lax496464.Conditions 2 import Lax496464.EstOrder 3 import Lax496464.ProperInstances 4 … module docstring, 40 lines 45 46 namespace Lax496464.Greedy 47 48 open Lax496464.FlowShop Lax496464.FlowShop.Instance 49 50 variable (I : Instance) 51 52 /-- How many jobs of `Z` are due at or before `t`. -/ 53 def dueCount (Z : Finset I.Job) (t : ℤ) : ℕ := (Z.filter fun i => (I.d i : ℤ) ≤ t).card 54 55 /-- **Domination.** `S` dominates `S'` when, below every threshold, `S'` has no more due 56 dates than `S`. -/ 57 def SDom (S S' : Finset I.Job) : Prop := ∀ t : ℤ, dueCount I S' t ≤ dueCount I S t 58 59 /-- The first `k` jobs in earliest-start-time order. -/ 60 def firstJobs (k : ℕ) : Finset I.Job := Finset.univ.filter fun i : I.Job => (i : ℕ) < k 61 62 /-- **One greedy step**, with common preprocessing time `p`: from `A` to `next` on 63 reaching job `j`. Either `j` is added, or a job of largest due date is dropped from 64 `A ∪ {j}`. -/ 65 def Step (p : ℕ) (A next : Finset I.Job) (j : I.Job) : Prop := 66 (((A.card : ℤ) + 1) * p ≤ s j ∧ (running I A (s j)).card < I.machines ∧ 67 next = insert j A) ∨ 68 ((s j < ((A.card : ℤ) + 1) * p ∨ I.machines ≤ (running I A (s j)).card) ∧ 69 ∃ c ∈ insert j A, (∀ i ∈ insert j A, (I.d i : ℤ) ≤ (I.d c : ℤ)) ∧ 70 next = (insert j A).erase c) 71 72 end Lax496464.Greedy 73 -
thm✓
Lax496464.Lemma4Lemma 4
After the greedy has considered the first jobs, the set it holds is feasible, uses only those jobs, and dominates every feasible set of jobs among them. Consequently the set it holds at the end is a feasible set of largest possible cardinality, which is the correctness of the algorithm of Section 6.1.
1 import Lax496464.Greedy 2 … module docstring, 29 lines 32 33 namespace Lax496464.Lemma4 34 35 open Lax496464.FlowShop Lax496464.FlowShop.Instance 36 open Lax496464.EstOrder Lax496464.Greedy 37 38 variable (I : Instance) 39 40 /-- **Lemma 4.** Along any greedy run, the set held after `k` jobs is a feasible subset of 41 the first `k` jobs dominating every such feasible set. -/ 42 axiom greedy_dominating (hest : EstOrdered I) {p : ℕ} (hp : ∀ i : I.Job, I.p i = p) 43 (hq : ∀ i : I.Job, 0 < I.q i) 44 (S : ℕ → Finset I.Job) (h0 : S 0 = ∅) 45 (hrun : ∀ k, ∀ hk : k < I.jobs, Step I p (S k) (S (k + 1)) ⟨k, hk⟩) : 46 ∀ k ≤ I.jobs, 47 Feasible I (S k) ∧ S k ⊆ firstJobs I k ∧ 48 ∀ B : Finset I.Job, B ⊆ firstJobs I k → Feasible I B → SDom I (S k) B 49 50 /-- **The greedy is optimal.** Its final set is a feasible set of largest cardinality. -/ 51 axiom greedy_card_max (hest : EstOrdered I) {p : ℕ} (hp : ∀ i : I.Job, I.p i = p) 52 (hq : ∀ i : I.Job, 0 < I.q i) 53 (S : ℕ → Finset I.Job) (h0 : S 0 = ∅) 54 (hrun : ∀ k, ∀ hk : k < I.jobs, Step I p (S k) (S (k + 1)) ⟨k, hk⟩) : 55 Feasible I (S I.jobs) ∧ 56 ∀ B : Finset I.Job, Feasible I B → B.card ≤ (S I.jobs).card 57 58 end Lax496464.Lemma4 59 -
no assumptions
Lemma 4, by induction along the run. Three cases: the job is added, the job is added and another dropped, or the job is added and itself dropped again.
-
The Integer Program of Section 6.2
When all preprocessing times are equal to , a set of jobs is feasible exactly when it satisfies two families of linear inequalities in its own indicator vector :
one of each per job . The first is Condition 1 — with equal preprocessing times, the time the first stage has spent is the number of jobs it has run — and the second is Condition 2 in its depth form, tested at the start times, where the number of jobs alive can only increase.
Maximizing subject to these is therefore the problem itself, written as an integer program with constraints and variables.
1 import Lax496464.Conditions 2 import Lax496464.EstOrder 3 import Lax496464.ProperInstances 4 import Mathlib.Data.Matrix.Mul 5 … module docstring, 36 lines 42 43 namespace Lax496464.IntegerProgram 44 45 open Lax496464.FlowShop Lax496464.FlowShop.Instance 46 47 variable (I : Instance) 48 49 /-- The constraint matrix: the rows `inl j` are the prefix sums of constraint (6), the 50 rows `inr j` the jobs alive at `s j` of constraint (7). -/ 51 def matrix : Matrix (I.Job ⊕ I.Job) I.Job ℤ 52 | Sum.inl j, i => if i ≤ j then 1 else 0 53 | Sum.inr j, i => if s i ≤ s j ∧ s j < (I.d i : ℤ) then 1 else 0 54 55 /-- The right-hand sides: `⌊s j / p⌋` for (6) and `m` for (7). -/ 56 def rhs (p : ℕ) : I.Job ⊕ I.Job → ℤ 57 | Sum.inl j => s j / p 58 | Sum.inr _ => I.machines 59 60 /-- The `0/1` vector of a set of jobs. -/ 61 def indicator (Z : Finset I.Job) : I.Job → ℤ := fun i => if i ∈ Z then 1 else 0 62 63 end Lax496464.IntegerProgram 64 -
Consecutive Ones Implies Total Unimodularity
A matrix of zeros and ones has the consecutive ones property when the ones in each row occupy a consecutive block of columns. Such a matrix is totally unimodular: every square submatrix has determinant , or .
This is the theorem of Fulkerson and Gross, and it is what makes the integer program of Section 6.2 solvable as a linear program.
1 import Mathlib.LinearAlgebra.Matrix.Determinant.TotallyUnimodular 2 … module docstring, 27 lines 30 31 namespace Lax496464.ConsecutiveOnes 32 33 /-- The ones of each row occupy a consecutive block of columns. -/ 34 def HasConsecutiveOnes {m n : Type*} [LE n] (A : Matrix m n ℤ) : Prop := 35 (∀ r c, A r c = 0 ∨ A r c = 1) ∧ 36 ∀ r c₁ c c₂, c₁ ≤ c → c ≤ c₂ → A r c₁ = 1 → A r c₂ = 1 → A r c = 1 37 38 /-- **Fulkerson–Gross.** A matrix with the consecutive ones property is totally 39 unimodular. -/ 40 axiom isTotallyUnimodular {m n : Type*} [LinearOrder n] {A : Matrix m n ℤ} 41 (h : HasConsecutiveOnes A) : A.IsTotallyUnimodular 42 43 end Lax496464.ConsecutiveOnes 44 -
no assumptions
Fulkerson–Gross. Sort the chosen columns, which changes the determinant only by a sign; each row of the sorted submatrix is then an interval of ones, and the determinant of such a matrix is , or by induction on subtracting consecutive rows.
-
thm✓
Lax496464.Lemma5Lemma 5
On a proper instance the jobs alive at any one instant form a consecutive block of the earliest-start-time order, so the constraint matrix of Section 6.2 has the consecutive ones property: the first family of rows is a family of prefixes, and the second is a family of blocks.
With the theorem of Fulkerson and Gross this makes the matrix totally unimodular, which is what lets the integer program be solved as a linear program.
The correspondence the section asserts — that the feasible solutions of the program are exactly the feasible sets — is stated here too. It holds on every instance with equal, positive preprocessing times and nonnegative start times, proper or not.
- thm✓
Lax496464.Lemma5(1st statement) - thm✓
Lax496464.Lemma5(2nd statement) - thm✓
Lax496464.Lemma5(3rd statement) - thm✓
Lax496464.Lemma5(4th statement)
1 import Lax496464.ConsecutiveOnes 2 import Lax496464.IntegerProgram 3 … module docstring, 30 lines 34 35 namespace Lax496464.Lemma5 36 37 open Lax496464.FlowShop Lax496464.FlowShop.Instance 38 open Lax496464.EstOrder Lax496464.ProperInstances 39 open Lax496464.ConsecutiveOnes Lax496464.IntegerProgram 40 open Matrix 41 42 variable (I : Instance) 43 44 /-- **The program is the problem.** With equal positive preprocessing times and 45 nonnegative start times, a set of jobs is feasible exactly when its indicator vector 46 satisfies both families of constraints. -/ 47 axiom ilp_correct (hest : EstOrdered I) {p : ℕ} (hp : ∀ i : I.Job, I.p i = p) (hp0 : 0 < p) 48 (hq : ∀ i : I.Job, 0 < I.q i) (hs : ∀ j : I.Job, 0 ≤ s j) (Z : Finset I.Job) : 49 Feasible I Z ↔ ∀ r, (matrix I *ᵥ indicator I Z) r ≤ rhs I p r 50 51 /-- **The optimum is the optimum.** A `0/1` vector satisfying the constraints with 52 objective value `W` is exactly a feasible set of weight `W`. -/ 53 axiom ilp_optimum (hest : EstOrdered I) {p : ℕ} (hp : ∀ i : I.Job, I.p i = p) (hp0 : 0 < p) 54 (hq : ∀ i : I.Job, 0 < I.q i) (hs : ∀ j : I.Job, 0 ≤ s j) (W : ℕ) : 55 (∃ x : I.Job → ℤ, (∀ i, x i = 0 ∨ x i = 1) ∧ 56 (∀ r, (matrix I *ᵥ x) r ≤ rhs I p r) ∧ ∑ i, (I.w i : ℤ) * x i = W) ↔ 57 ∃ Z : Finset I.Job, Feasible I Z ∧ weight I Z = W 58 59 /-- **Lemma 5.** On a proper instance the constraint matrix has the consecutive ones 60 property. -/ 61 axiom lemma5 (hest : EstOrdered I) (h : Proper I) : HasConsecutiveOnes (matrix I) 62 63 /-- The constraint matrix of a proper instance is totally unimodular. -/ 64 axiom matrix_isTotallyUnimodular (hest : EstOrdered I) (h : Proper I) : 65 (matrix I).IsTotallyUnimodular 66 67 end Lax496464.Lemma5 68 - thm✓
-
no assumptions
Rows are prefixes of the index order, so their ones are consecutive outright. Rows list the jobs alive at , and on a proper instance those are consecutive in the earliest-start-time order — an interval strictly inside another cannot occur, so the alive set at any instant is a block.
-
Corollary 3
When all preprocessing times are equal, the problem is solved in time. With the total preprocessing time a partial solution has spent is determined by how many jobs it has selected, so the instant the dual table runs over takes only values, and the bound of the second corollary becomes .
1 import Lax496464.Problems 2 import Lax496464.ProperInstances 3 … module docstring, 26 lines 30 31 namespace Lax496464.Corollary3 32 33 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 34 open Lax496464.ProperInstances 35 open Lax808846.Ram Lax808846.RamComputes 36 37 open Classical in 38 /-- **Corollary 3.** With equal preprocessing times the problem is decided within 39 `c · (n+1)^(m+1)` instructions, plus the cost of sorting. -/ 40 axiom corollary3_time : 41 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 42 ComputesInTime w prog 43 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 44 (∃ I W, EncodesDecisionInstance x I W ∧ Uniform I) ∧ 45 c * (jobCount x + 1) ^ (machineCount x + 1) ≤ 2 ^ w ∧ 46 (∀ j < jobCount x, 0 < procTime x j)} 47 (fun x => if Yes x then [1] else [0]) 48 (fun x => c * (jobCount x + 1) ^ (machineCount x + 1) + c * sortCost x) 49 50 end Lax496464.Corollary3 51 -
no assumptions
Corollary 3 as a word RAM program: the dual table of Section 3 for equal preprocessing times. Read the word, sort the jobs by start time, and fill the table over sets of thresholds written as base- numbers exactly as for Theorem 2, but with rows the instants (the instant a partial solution has spent after selecting jobs) instead of the weights: the cell for the instant is , the second term present when . The guard is tested as , where is computed once per job by a division, so the machine never forms the product (only the entries of the word are bounded by the word length). The table has entries, filled from the largest number down, and the answer is one entry. The domain restricts to positive processing times, the paper's standing assumption for Lemma 1's recursion.
-
Theorem 4
Two results for the case of equal preprocessing times.
Without weights, the greedy of Section 6.1 solves the problem in time, which is the cost of putting the jobs into earliest-start-time order; everything after that is one pass.
With weights, on a proper instance, the integer program of Section 6.2 has a totally unimodular constraint matrix and can therefore be solved as a linear program, in polynomial time. That case is stated here only through its first half; see the notes.
1 import Lax496464.Problems 2 import Lax496464.ProperInstances 3 … module docstring, 46 lines 50 51 namespace Lax496464.Theorem4 52 53 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 54 open Lax496464.ProperInstances 55 open Lax808846.Ram Lax808846.RamComputes 56 57 open Classical in 58 /-- **Theorem 4, the unweighted case.** With equal preprocessing times and unit weights, 59 the problem is decided within `c · n log n` instructions. -/ 60 axiom theorem4_greedy_time : 61 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 62 ComputesInTime w prog 63 {x | x ∈ DecisionInstances ∧ Fits c w x ∧ 64 (∃ I W, EncodesDecisionInstance x I W ∧ Uniform I) ∧ 65 (∀ j < jobCount x, wt x j = 1) ∧ (∀ j < jobCount x, 0 < procTime x j)} 66 (fun x => if Yes x then [1] else [0]) 67 (fun x => c * sortCost x) 68 69 end Lax496464.Theorem4 70 -
no assumptions
The greedy of Section 6.1 as a word RAM program: read the word, sort the jobs by start time (), then consider the jobs in that order, keeping the running jobs in two maximum trees over the job numbers — one keyed by due date (whose root gives the job to drop) and one by (whose root gives the next job to end) — and counting the members of the set that have already ended. Every step costs , so the whole run is within . The statement is exactly the concept's, including its positive-processing-time clause (the paper's standing assumption for Lemma 4).
-
Rounding the Weights, and What an Approximation Scheme Delivers
The rounding behind the fifth theorem, and the shape of the guarantee it gives.
Rounding every weight up to a multiple of and dividing by leaves an instance with the same jobs whose weights are smaller by a factor of about . A set that is optimal for the rounded weights loses, against the true optimum, at most per job selected, hence at most in total; and since the optimum is at least the largest single weight, choosing so that is a small fraction of the largest weight makes the loss a small fraction of the optimum. That is the chain of inequalities (8), and what it delivers is stated with the theorem that uses it.
An approximation scheme is a program that is handed an instance together with a positive integer and returns a number that a feasible set reaches, and that is within a factor of the optimum.
1 import Lax496464.Problems 2 … module docstring, 45 lines 48 49 namespace Lax496464.Fptas 50 51 open Lax496464.FlowShop Lax496464.FlowShop.Instance 52 open Lax496464.WordEncoding Lax496464.Problems 53 open Lax808846.Ram 54 55 variable (I : Instance) 56 57 /-- The largest weight of a single job. -/ 58 def wmax : ℕ := ((List.finRange I.jobs).map I.w).foldr max 0 59 60 /-- The instance with every weight rounded up to a multiple of `k` and divided by it. -/ 61 def rescale (k : ℕ) : Instance where 62 jobs := I.jobs 63 machines := I.machines 64 p := I.p 65 q := I.q 66 d := I.d 67 w j := (I.w j + (k - 1)) / k 68 69 variable {I} 70 71 variable (I) 72 73 /-- The word `x` presents the instance `I` together with the accuracy `e`. -/ 74 def EncodesApprox (x : List ℕ) (e : ℕ) : Prop := 75 ∃ y, x = y ++ [e] ∧ 1 ≤ e ∧ EncodesInstance y I 76 77 variable {I} 78 79 /-- The words that present an instance together with an accuracy. -/ 80 def ApproxInstances : Set (List ℕ) := {x | ∃ I e, EncodesApprox I x e} 81 82 /-- The output `y` is an acceptable answer on the input `x`: a number that some feasible set 83 reaches, within a factor `1 − 1/e` of the optimum. -/ 84 def Delivers (x y : List ℕ) : Prop := 85 ∃ (I : Instance) (e W : ℕ), EncodesApprox I x e ∧ y = [W] ∧ 86 HasWeight I W ∧ (e - 1) * optimum I ≤ e * W 87 88 /-- At word length `w`, on every admissible input, the program halts within `T x` 89 instructions having written an acceptable answer. -/ 90 def ApproximatesInTime (w : ℕ) (prog : Program) (D : Set (List ℕ)) 91 (T : List ℕ → ℕ) : Prop := 92 ∀ x ∈ D, ∃ (y : List ℕ) (t : ℕ), t ≤ T x ∧ RunsTo w prog x y t ∧ Delivers x y 93 94 end Lax496464.Fptas 95 -
Theorem 5
The problem admits a fully polynomial-time approximation scheme whenever one of the number of machines, the width and the largest processing time is bounded.
Rounding the weights as in (8) leaves a threshold of order to search, where is the reciprocal of the accuracy, so each of the three exact programs — the table of Section 3, the endpoint sweep and the profile sweep — becomes an approximation scheme whose running time is that of the program with replaced by .
- thm✓
Lax496464.Theorem5(1st statement) - thm✓
Lax496464.Theorem5(2nd statement) - thm✓
Lax496464.Theorem5(3rd statement) - thm✓
Lax496464.Theorem5(4th statement)
1 import Lax496464.Fptas 2 … module docstring, 54 lines 57 58 namespace Lax496464.Theorem5 59 60 open Lax496464.WordEncoding Lax496464.Problems Lax496464.ParameterizedComplexity 61 open Lax496464.Fptas Lax496464.FlowShop Lax496464.FlowShop.Instance 62 open Lax808846.Ram 63 64 /-- **Chain (8).** If `e · k · n ≤ w_max` and `Zs` is optimal for the weights rounded by 65 `k`, then `Zs` is within a factor `1 − 1/e` of the true optimum. -/ 66 axiom rescale_approx (I : Instance) {e k : ℕ} (he : 1 ≤ e) (hk : 1 ≤ k) 67 (hm : 0 < I.machines) (hpre : ∀ j : I.Job, (I.p j : ℤ) ≤ s j) 68 (hkn : e * k * I.jobs ≤ wmax I) (Zs : Finset I.Job) (hZs : Feasible I Zs) 69 (hopt : ∀ Z : Finset I.Job, Feasible I Z → 70 weight (rescale I k) Z ≤ weight (rescale I k) Zs) : 71 (e - 1) * optimum I ≤ e * weight I Zs 72 73 /-- **Theorem 5, from the table of Section 3.** An approximation scheme running within 74 `c · (n+1)² · (e+1) · (n+1)^m` instructions, plus the cost of sorting. -/ 75 axiom theorem5_byMachines : 76 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 77 ApproximatesInTime w prog 78 {x | x ∈ ApproxInstances ∧ Fits c w x ∧ 79 c * ((List.range (jobCount x)).map (wt x)).sum ≤ 2 ^ w ∧ 80 (∀ j < jobCount x, 0 < procTime x j) ∧ 81 c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 82 (jobCount x + 1) ^ machineCount x ≤ 2 ^ w} 83 (fun x => c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 84 (jobCount x + 1) ^ machineCount x + c * sortCost x) 85 86 /-- **Theorem 5, from the endpoint sweep.** An approximation scheme running within 87 `c · (n+1)² · (e+1) · 2^ω · (n+1)` instructions, plus the cost of sorting. -/ 88 axiom theorem5_byWidth : 89 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 90 ApproximatesInTime w prog 91 {x | x ∈ ApproxInstances ∧ Fits c w x ∧ 92 c * ((List.range (jobCount x)).map (wt x)).sum ≤ 2 ^ w ∧ 93 (∀ j < jobCount x, 0 < procTime x j) ∧ 94 c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 2 ^ widthOf x * 95 (jobCount x + 1) ≤ 2 ^ w} 96 (fun x => c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 2 ^ widthOf x * 97 (jobCount x + 1) + c * sortCost x) 98 99 /-- **Theorem 5, from the profile sweep.** An approximation scheme running within 100 `c · (n+1)² · (e+1) · (m+1)^q_max · (n+1)` instructions, plus the cost of sorting. -/ 101 axiom theorem5_byQmax : 102 ∃ (prog : Program) (c : ℕ), ∀ w : ℕ, 103 ApproximatesInTime w prog 104 {x | x ∈ ApproxInstances ∧ Fits c w x ∧ 105 c * ((List.range (jobCount x)).map (wt x)).sum ≤ 2 ^ w ∧ 106 (∀ j < jobCount x, 0 < procTime x j) ∧ 107 c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 108 (machineCount x + 1) ^ qmaxOf x * (jobCount x + 1) ≤ 2 ^ w} 109 (fun x => c * (jobCount x + 1) ^ 2 * (threshold x + 1) * 110 (machineCount x + 1) ^ qmaxOf x * (jobCount x + 1) + c * sortCost x) 111 112 end Lax496464.Theorem5 113 - thm✓
-
no assumptions
Chain (8). Rounding up loses less than per selected job, so at most in all; turns that into , and — the heaviest job is feasible on its own — turns it into .
-
no assumptions
The table of Section 3 as a word RAM approximation scheme. Read the instance and the accuracy (in the place of the threshold), sort the jobs by start time, build the sorted arrays (as in the exact program), zero the weight of every unfit job, take , replace every weight by , set the threshold , run the exact table program of Theorem 2 (, unchanged) on the rescaled weights, scan the finished column of the first indices for the last non-zero cell (for no job or no machine, ), and write if and otherwise (). The cost is that of the exact program with threshold , that is , plus the sort; the guarantee is .
-
no assumptions
The endpoint sweep of Section 4 as a word RAM approximation scheme. Read the instance and the accuracy (in the place of the threshold), sort the jobs by start time, build the sorted arrays and the due-date order (as in the exact program), zero the weight of every unfit job, take , replace every weight by , set the threshold , run the exact endpoint sweep (, unchanged) on the rescaled weights, scan the finished row for the last finite cell , and write if and otherwise (). The cost is that of the exact program with threshold , that is , plus the sort; the guarantee is .
-
no assumptions
The approximation scheme of the fifth theorem from the profile sweep: read the word, zero the weights of the jobs that cannot be preprocessed on their own, choose the rescaling , round the weights up to multiples of , run the profile sweep of the third theorem with the threshold that the rounding leaves, and write , where is the largest weight the rescaled table reaches ( itself when ).
-
Hitting Set
An instance consists of a universe and a family of subsets of it; together with an integer it asks whether some with meets every set of the family. Hitting Set is the problem the hardness results of this submission reduce from, parameterized by the solution size .
This is the problem [SP8] of Garey and Johnson. It contains Karp's Node Cover (problem 5 of his list) as the case of sets of size two. Karp's own Hitting Set (problem 15) is a different problem: it asks for a set meeting every member of the family in exactly one element.
The instances considered here are those with . The lower bound is what the construction of the reduction needs, and the upper bound is a normalization: a hitting set of size exactly cannot exist once exceeds the universe. Neither restriction costs anything — see the statement of the problem's hardness.
1 import Lax496464.ParameterizedComplexity 2 import Lax434930.PolynomialTime 3 import Mathlib.Data.List.FinRange 4 import Mathlib.Data.Nat.Bits 5 … module docstring, 55 lines 61 62 namespace Lax496464.HittingSet 63 64 open Lax496464.ParameterizedComplexity Lax434930.PolynomialTime 65 66 /-- An instance of Hitting Set: a universe `Fin n` and a family of `m` subsets of it. The 67 solution size is carried separately, as the parameter. -/ 68 structure Instance where 69 /-- The size `n` of the universe. -/ 70 n : ℕ 71 /-- The number `m` of sets in the family. -/ 72 m : ℕ 73 /-- The family `F₁, …, F_m`. -/ 74 F : Fin m → Finset (Fin n) 75 76 /-- `P` has a hitting set of size exactly `k`. -/ 77 def Instance.HasHittingSet (P : Instance) (k : ℕ) : Prop := 78 ∃ H : Finset (Fin P.n), H.card = k ∧ ∀ j : Fin P.m, ∃ i ∈ H, i ∈ P.F j 79 80 /-- The size of the universe declared by a word: its first entry. -/ 81 def universeSize (x : List ℕ) : ℕ := x.getD 0 0 82 83 /-- The number of sets declared by a word: its second entry. -/ 84 def setCount (x : List ℕ) : ℕ := x.getD 1 0 85 86 /-- The `i`-th offset: the `m+1` offsets follow the two header entries. -/ 87 def offset (x : List ℕ) (i : ℕ) : ℕ := x.getD (2 + i) 0 88 89 /-- The `t`-th entry of the member array, which follows the offsets. -/ 90 def member (x : List ℕ) (t : ℕ) : ℕ := x.getD (3 + setCount x + t) 0 91 92 /-- The solution size `k`: the entry following the member array. -/ 93 def solutionSize (x : List ℕ) : ℕ := 94 x.getD (3 + setCount x + offset x (setCount x)) 0 95 96 /-- The word `x` encodes the instance `P` with solution size `k`. -/ 97 structure Encodes (x : List ℕ) (P : Instance) (k : ℕ) : Prop where 98 /-- The word declares `P`'s universe. -/ 99 universeSize_eq : universeSize x = P.n 100 /-- The word declares `P`'s family. -/ 101 setCount_eq : setCount x = P.m 102 /-- The word is the two header entries, the `m+1` offsets, a member array as long as 103 the last offset says, and the solution size. -/ 104 length_eq : x.length = 4 + P.m + offset x P.m 105 /-- The block of the first set begins at the start of the member array. -/ 106 offset_zero : offset x 0 = 0 107 /-- The offsets are nondecreasing, so they cut the member array into one block per 108 set. -/ 109 offset_mono : ∀ j < P.m, offset x j ≤ offset x (j + 1) 110 /-- Every entry of the member array is an element of the universe. -/ 111 member_lt : ∀ t < offset x P.m, member x t < P.n 112 /-- The block of a set lists exactly its elements. -/ 113 mem_iff : ∀ (j : Fin P.m) (i : Fin P.n), 114 i ∈ P.F j ↔ ∃ t, offset x j ≤ t ∧ t < offset x (j + 1) ∧ member x t = i 115 /-- The word declares the solution size. -/ 116 solutionSize_eq : solutionSize x = k 117 /-- The solution size is at least two and at most the size of the universe. -/ 118 size_bounds : 2 ≤ k ∧ k ≤ P.n 119 /-- The universe is no larger than the word that presents it. -/ 120 universeSize_le : P.n ≤ x.length 121 122 /-- The words that encode an instance with its solution size. -/ 123 def Instances : Set (List ℕ) := {x | ∃ P k, Encodes x P k} 124 125 /-- **Hitting Set**, parameterized by the solution size. -/ 126 def byK : Problem where 127 Domain := Instances 128 Yes x := ∃ P k, Encodes x P k ∧ Instance.HasHittingSet P k 129 param x := solutionSize x 130 131 /-- The elements of the `j`-th set, in the order of the universe. -/ 132 def Instance.members (P : Instance) (j : Fin P.m) : List (Fin P.n) := 133 (List.finRange P.n).filter fun i => decide (i ∈ P.F j) 134 135 /-- A natural number as a binary word: its digits, least significant first, preceded by 136 their number in unary. -/ 137 def encodeNat (n : ℕ) : Word := 138 List.replicate n.bits.length true ++ [false] ++ n.bits 139 140 /-- An instance with its solution size as a binary word: the two counts, the solution 141 size, and then each set as its size followed by its members. -/ 142 def encodeInstance (P : Instance) (k : ℕ) : Word := 143 encodeNat P.n ++ encodeNat P.m ++ encodeNat k ++ 144 (List.finRange P.m).flatMap fun j => 145 encodeNat (P.F j).card ++ (P.members j).flatMap fun i => encodeNat i 146 147 /-- **Hitting Set** as a language: the binary words of the instances, with a solution size, 148 that have a hitting set of that size. -/ 149 def HittingSetLanguage : Language := 150 {w | ∃ (P : Instance) (k : ℕ), encodeInstance P k = w ∧ P.HasHittingSet k} 151 152 end Lax496464.HittingSet 153 -
NP-Hardness, and Strong NP-Hardness, of a Scheduling Problem
A property of instances is NP-hard if every language in NP has a polynomial-time many-one reduction to it, the reduction's output being an instance in the binary encoding.
It is strongly NP-hard if such a reduction exists whose emitted instances have all their numbers bounded by a fixed polynomial in the length of the input. A strongly NP-hard problem admits no pseudo-polynomial algorithm unless : an algorithm polynomial in the magnitudes of the numbers would be polynomial in the input length on the image of such a reduction.
1 import Lax496464.BinaryEncoding 2 import Lax434930.NondeterministicPolynomialTime 3 … module docstring, 39 lines 43 44 namespace Lax496464.NPHardness 45 46 open Lax496464.FlowShop Lax496464.BinaryEncoding 47 open Lax434930.PolynomialTime Lax434930.NondeterministicPolynomialTime 48 49 /-- A decision problem on instances with a threshold. -/ 50 abbrev Problem := Instance → ℕ → Prop 51 52 /-- `Q` is **NP-hard**: every language in NP reduces to it in polynomial time. -/ 53 def NPHard (Q : Problem) : Prop := 54 ∀ A : Language, A ∈ NP → 55 ∃ f : Word → Instance × ℕ, 56 Nonempty (Turing.TM2ComputableInPolyTime id 57 (fun z : Instance × ℕ => encodeDecisionInstance z.1 z.2) f) ∧ 58 ∀ x, x ∈ A ↔ Q (f x).1 (f x).2 59 60 /-- `Q` is **strongly NP-hard**: it is NP-hard by a reduction whose emitted instances 61 have all their numbers, and their threshold, bounded by a fixed polynomial in the length 62 of the input. -/ 63 def StronglyNPHard (Q : Problem) : Prop := 64 ∀ A : Language, A ∈ NP → 65 ∃ (f : Word → Instance × ℕ) (c : ℕ), 66 Nonempty (Turing.TM2ComputableInPolyTime id 67 (fun z : Instance × ℕ => encodeDecisionInstance z.1 z.2) f) ∧ 68 (∀ x, max (maxNumber (f x).1) (f x).2 ≤ (x.length + 2) ^ c) ∧ 69 ∀ x, x ∈ A ↔ Q (f x).1 (f x).2 70 71 /-- `Q` is **strongly NP-hard on `C`**: the same, by a reduction all of whose outputs 72 lie in `C`. -/ 73 def StronglyNPHardOn (Q : Problem) (C : Instance → Prop) : Prop := 74 ∀ A : Language, A ∈ NP → 75 ∃ (f : Word → Instance × ℕ) (c : ℕ), 76 Nonempty (Turing.TM2ComputableInPolyTime id 77 (fun z : Instance × ℕ => encodeDecisionInstance z.1 z.2) f) ∧ 78 (∀ x, max (maxNumber (f x).1) (f x).2 ≤ (x.length + 2) ^ c) ∧ 79 (∀ x, C (f x).1) ∧ 80 ∀ x, x ∈ A ↔ Q (f x).1 (f x).2 81 82 end Lax496464.NPHardness 83 -
Parameterized Problems and FPT-Reductions on a Word RAM
A parameterized problem is a set of admissible input words, a yes-instance predicate on them, and a parameter read off the word. It is fixed-parameter tractable if one word RAM program decides it, on every admissible word of parameter , within instructions, for a constant and a function of the parameter alone.
An fpt-reduction from to is a map on words that sends admissible words to admissible words, preserves and reflects yes-instances, raises the parameter by at most a function of it, and is computed by one word RAM program within the same kind of bound. Fpt-reductions compose, and a problem that is fixed-parameter tractable and receives an fpt-reduction makes the source problem fixed-parameter tractable as well.
1 import Lax808846.RamComputes 2 … module docstring, 49 lines 52 53 namespace Lax496464.ParameterizedComplexity 54 55 open Lax808846.Ram Lax808846.RamComputes 56 57 /-- A parameterized problem: the words that encode an instance, which of them are 58 yes-instances, and the parameter each one carries. -/ 59 structure Problem where 60 /-- The words that encode an instance. A program may do anything on the others. -/ 61 Domain : Set (List ℕ) 62 /-- The yes-instances. -/ 63 Yes : List ℕ → Prop 64 /-- The parameter, read off the word. -/ 65 param : List ℕ → ℕ 66 67 /-- The word `x` fits at word length `w`, with room for `c` times its length: every entry 68 `v` of `x` satisfies `c * (x.length + v + 1) ≤ 2 ^ w`. -/ 69 def Fits (c w : ℕ) (x : List ℕ) : Prop := ∀ v ∈ x, c * (x.length + v + 1) ≤ 2 ^ w 70 71 open Classical in 72 /-- At every word length, the program decides `P` on every admissible word that fits, 73 within `c * g k * (|x| + 1) ^ c` instructions, where `k` is the word's parameter. It writes 74 `1` for a yes-instance and `0` for a no-instance. -/ 75 def Decides (P : Problem) (prog : Program) (c : ℕ) (g : ℕ → ℕ) : Prop := 76 ∀ w : ℕ, ComputesInTime w prog 77 {x | x ∈ P.Domain ∧ Fits c w x} 78 (fun x => if P.Yes x then [1] else [0]) 79 (fun x => c * g (P.param x) * (x.length + 1) ^ c) 80 81 /-- `P` is **fixed-parameter tractable**: one program and one constant decide it within 82 `c * g k * (|x| + 1) ^ c` instructions, for some function `g` of the parameter alone. -/ 83 def FPT (P : Problem) : Prop := ∃ (prog : Program) (c : ℕ) (g : ℕ → ℕ), Decides P prog c g 84 85 /-- The map `f` is an fpt-reduction from `P` to `Q`, computed by `prog` within 86 `c * g k * (|x| + 1) ^ c` instructions and raising the parameter by at most `h`. -/ 87 structure IsFptReduction (P Q : Problem) (f : List ℕ → List ℕ) (prog : Program) 88 (c : ℕ) (g h : ℕ → ℕ) : Prop where 89 /-- The image of an admissible word is admissible. -/ 90 maps_domain : ∀ x ∈ P.Domain, f x ∈ Q.Domain 91 /-- Yes-instances go to yes-instances, and no-instances to no-instances. -/ 92 correct : ∀ x ∈ P.Domain, (P.Yes x ↔ Q.Yes (f x)) 93 /-- The new parameter is bounded by a function of the old one alone. -/ 94 param_le : ∀ x ∈ P.Domain, Q.param (f x) ≤ h (P.param x) 95 /-- At every word length, the program computes `f` on every admissible word that fits 96 and whose image fits, within the stated bound. -/ 97 time : ∀ w : ℕ, ComputesInTime w prog 98 {x | x ∈ P.Domain ∧ Fits c w x ∧ Fits c w (f x)} 99 f (fun x => c * g (P.param x) * (x.length + 1) ^ c) 100 101 /-- `P` **fpt-reduces** to `Q`. -/ 102 def FptReduces (P Q : Problem) : Prop := 103 ∃ (f : List ℕ → List ℕ) (prog : Program) (c : ℕ) (g h : ℕ → ℕ), 104 IsFptReduction P Q f prog c g h 105 106 @[inherit_doc] infix:50 " ≤fpt " => FptReduces 107 108 end Lax496464.ParameterizedComplexity 109 -
W[2]-Hardness
The scheduling development uses to state the existence of an FPT-reduction from Hitting Set, parameterized by solution size, to . Its interpretation as W[2]-hardness follows from the W[2]-hardness of Hitting Set.
1 import Lax496464.HittingSet 2 … module docstring, 17 lines 20 21 namespace Lax496464.W2Hardness 22 23 open Lax496464.ParameterizedComplexity 24 25 /-- `P` is **W[2]-hard**: Hitting Set, parameterized by the solution size, fpt-reduces to 26 it. -/ 27 def W2Hard (P : Problem) : Prop := HittingSet.byK ≤fpt P 28 29 end Lax496464.W2Hardness 30 -
The Construction of Section 8
The shop built from a Hitting Set instance over with solution size . It has second-stage machines and unit weights.
The time axis is measured in the unit and divided into segments, each of which is divided into epochs, one per set of the family. The epoch of segment and set is numbered and occupies the interval from to ; the squares make each epoch exactly long, which is what the jobs are cut to fit.
Each epoch carries three families of jobs.
- One selection job for every element of . It needs no preprocessing, occupies the whole epoch, and is due at its end, offset by .
- Two dummy jobs for every element of the universe. The first occupies the first of the epoch, the second the last ; both need units of preprocessing, which is what limits how many of them an epoch can afford.
A hitting set of size selects, in every epoch, one selection job — the one for the element hitting that epoch's set — and dummies, one pair for each of the other elements. The target is just-in-time jobs.
1 import Lax496464.FlowShop 2 import Lax496464.HittingSet 3 … module docstring, 73 lines 77 78 namespace Lax496464.Construction 79 80 open Lax496464.FlowShop 81 82 variable (P : HittingSet.Instance) (k : ℕ) 83 84 /-- `R = k(n − 1) + 2`, the number of segments. The paper's value is one smaller; see the 85 notes above. -/ 86 def R : ℕ := k * (P.n - 1) + 2 87 88 /-- `Q = (k − 1)(n + 1)`, the unit the whole time axis is measured in. -/ 89 def Q : ℕ := (k - 1) * (P.n + 1) 90 91 /-- `g(r,j) = rm + j`, the position of the epoch of segment `r` and set `j`, counted from 92 one. -/ 93 def g (r j : ℕ) : ℕ := r * P.m + (j + 1) 94 95 /-- `G(r,j) = g(r,j)²Q`, the instant that epoch begins. -/ 96 def G (r j : ℕ) : ℕ := g P r j ^ 2 * Q P k 97 98 /-- The membership pairs `(j, i)` with `i ∈ F j`, enumerated. Each of them contributes one 99 selection job per segment. -/ 100 def memberList : List (ℕ × ℕ) := 101 (List.finRange P.m).flatMap fun j => 102 (P.members j).map fun i : Fin P.n => ((j : ℕ), (i : ℕ)) 103 104 /-- The number of selection jobs: one per segment and membership pair. -/ 105 def selCount : ℕ := R P k * (memberList P).length 106 107 /-- The number of jobs in one dummy block: one per segment, set and universe element. -/ 108 def dumCount : ℕ := R P k * P.m * P.n 109 110 /-- The number of jobs of the constructed shop. -/ 111 def numJobs : ℕ := selCount P k + 2 * dumCount P k 112 113 /-- Job `t` described: which of the three families it belongs to — `0` selection, `1` the 114 first dummy, `2` the second — and its segment, set and element. -/ 115 def slot (t : ℕ) : ℕ × ℕ × ℕ × ℕ := 116 if t < selCount P k then 117 let e := (memberList P).getD (t % (memberList P).length) (0, 0) 118 (0, t / (memberList P).length, e.1, e.2) 119 else 120 let u := t - selCount P k 121 let v := if u < dumCount P k then u else u - dumCount P k 122 (if u < dumCount P k then 1 else 2, 123 v / (P.m * P.n), v % (P.m * P.n) / P.n, v % P.n) 124 125 /-- Which of the three families job `t` belongs to. -/ 126 def family (t : ℕ) : ℕ := (slot P k t).1 127 128 /-- The segment of job `t`. -/ 129 def seg (t : ℕ) : ℕ := (slot P k t).2.1 130 131 /-- The set of the family job `t` belongs to. -/ 132 def setIdx (t : ℕ) : ℕ := (slot P k t).2.2.1 133 134 /-- The universe element of job `t`. -/ 135 def elem (t : ℕ) : ℕ := (slot P k t).2.2.2 136 137 /-- Preprocessing times: a selection job needs none, a dummy of epoch `(r,j)` needs 138 `g(r,j)(n+1)`, which is the paper's `g(r,j)Q/(k−1)`. -/ 139 def jp (t : ℕ) : ℕ := 140 if family P k t = 0 then 0 else g P (seg P k t) (setIdx P k t) * (P.n + 1) 141 142 /-- Processing times: a selection job fills its whole epoch, and the two dummies split it 143 in the ratio `g : g+1`. -/ 144 def jq (t : ℕ) : ℕ := 145 if family P k t = 0 then (2 * g P (seg P k t) (setIdx P k t) + 1) * Q P k 146 else if family P k t = 1 then g P (seg P k t) (setIdx P k t) * Q P k 147 else (g P (seg P k t) (setIdx P k t) + 1) * Q P k 148 149 /-- Due dates: the first dummy is due a `g(r,j)Q` into its epoch, the other two at its 150 end; all three are offset by the element they carry. -/ 151 def jd (t : ℕ) : ℕ := 152 if family P k t = 1 then 153 G P k (seg P k t) (setIdx P k t) + g P (seg P k t) (setIdx P k t) * Q P k 154 + (elem P k t + 1) 155 else 156 G P k (seg P k t) (setIdx P k t) + (2 * g P (seg P k t) (setIdx P k t) + 1) * Q P k 157 + (elem P k t + 1) 158 159 /-- The shop built from `P` and `k`: `k` machines and unit weights. -/ 160 def construct : FlowShop.Instance where 161 jobs := numJobs P k 162 machines := k 163 p t := jp P k t 164 q t := jq P k t 165 d t := jd P k t 166 w _ := 1 167 168 /-- The number of just-in-time jobs the construction asks for: one selection job and 169 `2(k−1)` dummies in each of the `R·m` epochs. -/ 170 def target : ℕ := R P k * P.m * (2 * k - 1) 171 172 end Lax496464.Construction 173 -
Theorem 1
Just-in-time scheduling in a two-stage flexible flow shop is strongly NP-hard, already when every weight is one.
The reduction is from Hitting Set. For , the shop built from a family over admits just-in-time jobs exactly when the family has a hitting set of size .
One direction schedules: a hitting set assigns to each of its elements one machine, which runs in every epoch either the selection job of the element that hits that epoch's set or the two dummies of the element it is responsible for, and the preprocessing of those dummies fits into the epoch exactly. The other direction extracts: the preprocessing budget of an epoch admits at most dummies, so a solution of the target size holds exactly one selection job and dummies in every epoch, the element a machine is responsible for never decreases from one segment to the next, and with more than gaps between segments some gap sees no change at all — the elements of that segment hit every set.
The numbers of the constructed shop are polynomial in , and , and hence in the length of the Hitting Set instance, so the hardness is strong: no algorithm polynomial in the magnitudes of the due dates can exist unless .
1 import Lax496464.Construction 2 import Lax496464.NPHardness 3 … module docstring, 45 lines 49 50 namespace Lax496464.Theorem1 51 52 open Lax496464.FlowShop Lax496464.FlowShop.Instance 53 open Lax496464.HittingSet Lax496464.Construction Lax496464.NPHardness 54 55 /-- **Theorem 1, the construction.** For `2 ≤ k ≤ n`, the constructed shop has a feasible 56 set of `R·m·(2k−1)` just-in-time jobs exactly when `P` has a hitting set of size `k`. -/ 57 axiom construct_correct (P : HittingSet.Instance) (k : ℕ) (hk : 2 ≤ k) (hkn : k ≤ P.n) : 58 Instance.HasHittingSet P k ↔ HasWeight (construct P k) (target P k) 59 60 /-- **Theorem 1.** Just-in-time scheduling in a two-stage flexible flow shop is strongly 61 NP-hard, already on instances all of whose weights are one. -/ 62 axiom stronglyNPHard_hasWeight : 63 StronglyNPHardOn (fun I W => HasWeight I W) fun I => ∀ j : I.Job, I.w j = 1 64 65 end Lax496464.Theorem1 66 -
no assumptions
Lemma 6 in one direction, Lemmas 7 to 9 in the other, transported along the numbering of the jobs.
-
Compose the NP-hardness of Hitting Set with the word RAM reduction of Section 8: the reduction reads the bits of the emitted instance, writes the bits of the constructed shop (, polynomial-time on the word RAM by , hence on a Turing machine), and the numbers of the shop are polynomial in the length of the original input.
-
Corollary 4
There exists an FPT-reduction from Hitting Set, parameterized by solution size, to just-in-time scheduling parameterized by the number of second-stage machines. The construction preserves the parameter: a hitting-set instance with solution size produces a shop with exactly machines.
The W[2]-hardness consequence follows from the W[2]-hardness of Hitting Set.
1 import Lax496464.Construction 2 import Lax496464.Problems 3 import Lax496464.W2Hardness 4 … module docstring, 19 lines 24 25 namespace Lax496464.Corollary4 26 27 open Lax496464.Problems Lax496464.W2Hardness 28 29 /-- **Corollary 4.** The problem is W[2]-hard with respect to the number of machines. -/ 30 axiom w2Hard_byMachines : W2Hard byMachines 31 32 end Lax496464.Corollary4 33 -
no assumptions
The construction of Section 8, written as a word RAM program, is an fpt-reduction from Hitting Set. Its correctness is carried to words; its running time is a polynomial of degree four in the length of the word, and the values it computes stay below a bound that the concept's two admissibility clauses provide.
-
The Reduction from Satisfiability to Hitting Set
A CNF formula on the variables becomes an instance of Hitting Set on the universe , in which stands for the literal and for . The family has one set for each variable and one set for each clause, holding the elements of its literals; the required size is .
A hitting set of size meets each of the disjoint pairs in exactly one element, so it is a truth assignment, and it meets the set of a clause exactly when the assignment satisfies the clause. This is the composition of Karp's reductions from satisfiability to Clique and from Clique to Node Cover, read on the literals rather than on the occurrences.
The reduction on words decodes a formula, builds the instance and encodes it; a word that encodes no formula is treated as the formula with one empty clause, whose instance has no hitting set.
1 import Lax496464.HittingSet 2 import Lax429075.Satisfiability 3 … module docstring, 31 lines 35 36 namespace Lax496464.HittingSetFromSat 37 38 open Lax429075.CNF Lax429075.Encoding Lax434930.PolynomialTime Lax496464.HittingSet 39 40 /-- One more than the largest variable index of the formula, and `1` for no literal. -/ 41 def bound (F : Formula) : ℕ := (F.flatMap id).foldr (fun l n => max (l.index + 1) n) 1 42 43 /-- The number of variables of the construction: the formula's, but at least two. -/ 44 def vars (F : Formula) : ℕ := max (bound F) 2 45 46 /-- The element standing for a literal: `2i` for `x_i`, `2i + 1` for `¬x_i`. -/ 47 def elem (l : Literal) : ℕ := 2 * l.index + if l.positive then 0 else 1 48 49 /-- The sets, as lists of elements: the pair of each variable, then the elements of each 50 clause. -/ 51 def sets (F : Formula) : List (List ℕ) := 52 (List.range (vars F)).map (fun i => [2 * i, 2 * i + 1]) ++ F.map fun C => C.map elem 53 54 /-- The instance of a formula: the universe `2·vars F`, one set per variable and one per 55 clause. -/ 56 def inst (F : Formula) : Instance where 57 n := 2 * vars F 58 m := vars F + F.length 59 F := fun j => Finset.univ.filter fun i : Fin (2 * vars F) => (i : ℕ) ∈ (sets F).getD j [] 60 61 /-- The instance with its solution size. -/ 62 def transform (F : Formula) : Instance × ℕ := (inst F, vars F) 63 64 /-- The formula a word encodes, and the formula with one empty clause if it encodes none. -/ 65 def parseF (w : Word) : Formula := (decodeCNF w).getD [[]] 66 67 /-- The reduction on words, to an instance with its solution size. -/ 68 def reduce (w : Word) : Instance × ℕ := transform (parseF w) 69 70 /-- The reduction on words, to the binary word of the instance. -/ 71 def reduceWord (w : Word) : Word := encodeInstance (reduce w).1 (reduce w).2 72 73 end Lax496464.HittingSetFromSat 74 -
Hitting Set Is NP-Hard
Every language in NP reduces to Hitting Set in polynomial time, by a reduction whose output instances stay of size polynomial in the length of the input and satisfy .
This is Karp's theorem. The reduction is Cook's theorem, followed by the reduction from satisfiability of : a formula is satisfiable exactly when its instance has a hitting set of the required size, and the reduction runs in polynomial time on a Turing machine writing the binary word of the instance. The theorem is stated twice: as a many-one reduction into the language of Hitting Set, and in the form the reduction of this submission consumes it.
The two restrictions on cost nothing. A hitting set of size exactly cannot exist once exceeds the universe, so the upper one is a normalization; for the lower one, pad an instance with one fresh element and the singleton set containing it, which forces that element into every hitting set and raises by one.
The universe of an emitted instance is no larger than plus the total size of the sets. The reduction from satisfiability satisfies this, since its pairs alone cover the universe. The clause is what lets the next reduction, which reads the word of the emitted instance, be polynomial-time in that word: the shop it builds has more than jobs, so a universe larger than the sets that present it — which a word of bits could name — could not be written by any machine polynomial in the word. It is the same restriction the admissible words of the fixed-parameter statements carry, that the universe is no larger than the word that presents it.
That the size of the emitted instance stays polynomially bounded is automatic for a polynomial-time reduction, since an instance cannot be larger than what was written. It is stated because it is what makes the composed reduction of this submission a strong NP-hardness statement: the numbers of the constructed shop are polynomial in , and , hence in the length of the original input.
- thm✓
Lax496464.HittingSetHardness(1st statement) - thm✓
Lax496464.HittingSetHardness(2nd statement) - thm✓
Lax496464.HittingSetHardness(3rd statement) - thm✓
Lax496464.HittingSetHardness(4th statement)
1 import Lax496464.HittingSetFromSat 2 import Lax434930.NondeterministicPolynomialTime 3 import Lax429075.Reductions 4 … module docstring, 51 lines 56 57 namespace Lax496464.HittingSetHardness 58 59 open Lax496464.HittingSet Lax496464.HittingSetFromSat Lax434930.PolynomialTime 60 open Lax434930.NondeterministicPolynomialTime Lax429075.CNF 61 62 /-- **Correctness of the reduction from satisfiability**: a formula is satisfiable exactly 63 when its instance has a hitting set of the required size. -/ 64 axiom fromSat_correct (F : Formula) : Satisfiable F ↔ (inst F).HasHittingSet (vars F) 65 66 /-- **The reduction from satisfiability runs in polynomial time**, as a Turing machine 67 writing the binary word of the instance. -/ 68 axiom fromSat_polyTime : 69 Nonempty (Turing.TM2ComputableInPolyTime id 70 (fun z : Instance × ℕ => encodeInstance z.1 z.2) reduce) 71 72 /-- **Hitting Set is NP-hard** (Karp): every language in NP has a polynomial-time 73 many-one reduction to it. -/ 74 axiom npHard : ∀ A : Language, A ∈ NP → Lax429075.Reductions.ManyOne A HittingSetLanguage 75 76 /-- **Hitting Set is NP-hard** (Karp), already on instances with `2 ≤ k ≤ n`, by a 77 reduction whose output stays polynomially bounded and whose universe is covered by its 78 sets. -/ 79 axiom hittingSet_npHard : 80 ∀ A : Language, A ∈ NP → 81 ∃ (f : Word → Instance × ℕ) (c : ℕ), 82 Nonempty (Turing.TM2ComputableInPolyTime id 83 (fun z : Instance × ℕ => encodeInstance z.1 z.2) f) ∧ 84 (∀ x, 2 ≤ (f x).2 ∧ (f x).2 ≤ (f x).1.n) ∧ 85 (∀ x, (f x).1.n + (f x).1.m ≤ (x.length + 2) ^ c) ∧ 86 (∀ x, (f x).1.n ≤ 4 + (f x).1.m + ∑ j : Fin (f x).1.m, ((f x).1.F j).card) ∧ 87 (∀ x, x ∈ A ↔ Instance.HasHittingSet (f x).1 (f x).2) 88 89 end Lax496464.HittingSetHardness 90 - thm✓
-
no assumptions
A satisfying assignment gives the set of the elements of its true literals, one per variable; conversely a hitting set of size meets each of the disjoint pairs in exactly one element, which fixes an assignment, and meeting the set of a clause is satisfying the clause.
-
The reduction is a word RAM program on the zeros and ones of its input: a finite-state scan decodes the formula into arrays of literal indices, signs and clause numbers; the three numbers and the pairs are written directly; for each clause the elements of its literals are marked and counted in one pass over the positions, and a sweep of the marks writes them in increasing order. Polynomial time on the word RAM transfers to a Turing machine, and the machine writing the word of the instance is one writing the instance.
-
Computability and polynomial-time equivalence of Turing machines and word RAMs
-
The Cook–Levin Theorem
-
The same reduction, writing the instance: the size clauses are those of the construction, composed with the polynomial length of the output of Cook's reduction.