|
139 | 139 | "ConsecutiveBlockMinimization": [Consecutive Block Minimization], |
140 | 140 | "ConsecutiveOnesSubmatrix": [Consecutive Ones Submatrix], |
141 | 141 | "SequencingToMinimizeMaximumCumulativeCost": [Sequencing to Minimize Maximum Cumulative Cost], |
| 142 | + "SequencingToMinimizeWeightedCompletionTime": [Sequencing to Minimize Weighted Completion Time], |
142 | 143 | "SequencingToMinimizeWeightedTardiness": [Sequencing to Minimize Weighted Tardiness], |
143 | 144 | "SequencingWithinIntervals": [Sequencing Within Intervals], |
144 | 145 | "SumOfSquaresPartition": [Sum of Squares Partition], |
@@ -3716,6 +3717,72 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], |
3716 | 3717 | ] |
3717 | 3718 | } |
3718 | 3719 |
|
| 3720 | +#{ |
| 3721 | + let x = load-model-example("SequencingToMinimizeWeightedCompletionTime") |
| 3722 | + let lengths = x.instance.lengths |
| 3723 | + let weights = x.instance.weights |
| 3724 | + let precs = x.instance.precedences |
| 3725 | + let ntasks = lengths.len() |
| 3726 | + let sol = x.optimal.at(0) |
| 3727 | + let opt = sol.metric.Valid |
| 3728 | + let lehmer = sol.config |
| 3729 | + let schedule = { |
| 3730 | + let avail = range(ntasks) |
| 3731 | + let result = () |
| 3732 | + for c in lehmer { |
| 3733 | + result.push(avail.at(c)) |
| 3734 | + avail = avail.enumerate().filter(((i, v)) => i != c).map(((i, v)) => v) |
| 3735 | + } |
| 3736 | + result |
| 3737 | + } |
| 3738 | + let starts = () |
| 3739 | + let finishes = () |
| 3740 | + let elapsed = 0 |
| 3741 | + for task in schedule { |
| 3742 | + starts.push(elapsed) |
| 3743 | + elapsed += lengths.at(task) |
| 3744 | + finishes.push(elapsed) |
| 3745 | + } |
| 3746 | + let total-time = elapsed |
| 3747 | + [ |
| 3748 | + #problem-def("SequencingToMinimizeWeightedCompletionTime")[ |
| 3749 | + Given a set $T$ of $n$ tasks, a processing-time function $l: T -> ZZ^+$, a weight function $w: T -> ZZ^+$, and a partial order $prec.eq$ on $T$, find a one-machine schedule minimizing $sum_(t in T) w(t) C(t)$, where $C(t)$ is the completion time of task $t$ and every precedence relation $t_i prec.eq t_j$ requires task $t_i$ to complete before task $t_j$ starts. |
| 3750 | + ][ |
| 3751 | + Sequencing to Minimize Weighted Completion Time is the single-machine precedence-constrained scheduling problem catalogued as SS4 in Garey & Johnson @garey1979, usually written $1 | "prec" | sum w_j C_j$. Lawler showed that arbitrary precedence constraints make the problem NP-complete, while series-parallel precedence orders admit an $O(n log n)$ algorithm @lawler1978. Without precedence constraints, Smith's ratio rule orders jobs by non-increasing $w_j / l_j$ and is optimal @smith1956. |
| 3752 | + |
| 3753 | + *Example.* Consider tasks with lengths $l = (#lengths.map(v => str(v)).join(", "))$, weights $w = (#weights.map(v => str(v)).join(", "))$, and precedence constraints #{precs.map(p => [$t_#(p.at(0)) prec.eq t_#(p.at(1))$]).join(", ")}. An optimal schedule is $(#schedule.map(t => $t_#t$).join(", "))$, with completion times $(#finishes.map(v => str(v)).join(", "))$ along the machine timeline and objective value $#opt$. |
| 3754 | + |
| 3755 | + #figure( |
| 3756 | + canvas(length: 1cm, { |
| 3757 | + import draw: * |
| 3758 | + let colors = (rgb("#4e79a7"), rgb("#e15759"), rgb("#76b7b2"), rgb("#f28e2b"), rgb("#59a14f")) |
| 3759 | + let scale = 0.55 |
| 3760 | + let row-h = 0.7 |
| 3761 | + |
| 3762 | + for (pos, task) in schedule.enumerate() { |
| 3763 | + let x0 = starts.at(pos) * scale |
| 3764 | + let x1 = finishes.at(pos) * scale |
| 3765 | + let color = colors.at(calc.rem(task, colors.len())) |
| 3766 | + rect((x0, -row-h / 2), (x1, row-h / 2), |
| 3767 | + fill: color.transparentize(30%), stroke: 0.4pt + color) |
| 3768 | + content(((x0 + x1) / 2, 0), text(7pt, $t_#task$)) |
| 3769 | + } |
| 3770 | + |
| 3771 | + let y-axis = -row-h / 2 - 0.22 |
| 3772 | + line((0, y-axis), (total-time * scale, y-axis), stroke: 0.4pt) |
| 3773 | + for t in range(total-time + 1) { |
| 3774 | + let x = t * scale |
| 3775 | + line((x, y-axis), (x, y-axis - 0.08), stroke: 0.4pt) |
| 3776 | + content((x, y-axis - 0.22), text(6pt, str(t))) |
| 3777 | + } |
| 3778 | + content((total-time * scale / 2, y-axis - 0.45), text(7pt)[time]) |
| 3779 | + }), |
| 3780 | + caption: [Optimal single-machine schedule for the canonical weighted-completion-time instance. Each block width equals the processing time $l_j$.], |
| 3781 | + ) <fig:stmwct> |
| 3782 | + ] |
| 3783 | + ] |
| 3784 | +} |
| 3785 | + |
3719 | 3786 | #{ |
3720 | 3787 | let x = load-model-example("SequencingToMinimizeWeightedTardiness") |
3721 | 3788 | let lengths = x.instance.lengths |
@@ -4898,6 +4965,28 @@ The following reductions to Integer Linear Programming are straightforward formu |
4898 | 4965 | _Solution extraction._ For each item $i$, find the unique $j$ with $x_(i j) = 1$; assign item $i$ to bin $j$. |
4899 | 4966 | ] |
4900 | 4967 |
|
| 4968 | +#reduction-rule("SequencingToMinimizeWeightedCompletionTime", "ILP")[ |
| 4969 | + Completion times are natural integer variables, precedence constraints compare those completion times directly, and one binary order variable per task pair enforces that a single machine cannot overlap two jobs. |
| 4970 | +][ |
| 4971 | + _Construction._ For each task $j$, introduce an integer completion-time variable $C_j$. For each unordered pair $i < j$, introduce a binary order variable $y_(i j)$ with $y_(i j) = 1$ meaning task $i$ finishes before task $j$. Let $M = sum_h l_h$. |
| 4972 | + |
| 4973 | + _Bounds._ $l_j <= C_j <= M$ for every task $j$, and $y_(i j) in {0, 1}$. |
| 4974 | + |
| 4975 | + _Precedence constraints._ If $i prec.eq j$, require $C_j - C_i >= l_j$. |
| 4976 | + |
| 4977 | + _Single-machine disjunction._ For every pair $i < j$, require |
| 4978 | + $C_j - C_i + M (1 - y_(i j)) >= l_j$ |
| 4979 | + and |
| 4980 | + $C_i - C_j + M y_(i j) >= l_i$. |
| 4981 | + Exactly one of the two orderings is therefore active. |
| 4982 | + |
| 4983 | + _Objective._ Minimize $sum_j w_j C_j$. |
| 4984 | + |
| 4985 | + _Correctness._ ($arrow.r.double$) Any feasible schedule defines completion times and pairwise order values satisfying the bounds, precedence inequalities, and disjunctive machine constraints; its weighted completion time is exactly the ILP objective. ($arrow.l.double$) Any feasible ILP solution assigns a strict order to every task pair and forbids overlap, so the completion times correspond to a valid single-machine schedule that respects all precedences. Minimizing the ILP objective therefore minimizes the original weighted completion-time objective. |
| 4986 | + |
| 4987 | + _Solution extraction._ Sort tasks by their completion times $C_j$ and encode that order back into the source schedule representation. |
| 4988 | +] |
| 4989 | + |
4901 | 4990 | #reduction-rule("TravelingSalesman", "ILP", |
4902 | 4991 | example: true, |
4903 | 4992 | example-caption: [Weighted $K_4$: the optimal tour $0 arrow 1 arrow 3 arrow 2 arrow 0$ with cost 80 is found by position-based ILP.], |
|
0 commit comments