1use crate::models::formula::KSatisfiability;
13use crate::models::misc::PreemptiveScheduling;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16use crate::variant::K3;
17
18#[derive(Debug, Clone)]
19struct UllmanConstruction {
20 time_limit: usize,
21 num_processors: usize,
22 positive_chains: Vec<Vec<usize>>,
23 #[cfg(any(test, feature = "example-db"))]
24 negative_chains: Vec<Vec<usize>>,
25 #[cfg(any(test, feature = "example-db"))]
26 positive_forcing: Vec<usize>,
27 #[cfg(any(test, feature = "example-db"))]
28 negative_forcing: Vec<usize>,
29 #[cfg(any(test, feature = "example-db"))]
30 clause_jobs: Vec<Vec<usize>>,
31 #[cfg(any(test, feature = "example-db"))]
32 filler_jobs_by_slot: Vec<Vec<usize>>,
33 precedences: Vec<(usize, usize)>,
34 num_jobs: usize,
35}
36
37fn time_limit(num_vars: usize) -> usize {
38 num_vars + 3
39}
40
41fn processor_upper_bound(num_vars: usize, num_clauses: usize) -> usize {
42 (2 * num_vars + 2).max(6 * num_clauses)
43}
44
45fn slot_capacities(num_vars: usize, num_clauses: usize) -> Vec<usize> {
46 let mut capacities = vec![0; time_limit(num_vars)];
47 capacities[0] = num_vars;
48 capacities[1] = 2 * num_vars + 1;
49 for capacity in capacities.iter_mut().take(num_vars + 1).skip(2) {
50 *capacity = 2 * num_vars + 2;
51 }
52 capacities[num_vars + 1] = num_clauses + num_vars + 1;
53 capacities[num_vars + 2] = 6 * num_clauses;
54 capacities
55}
56
57fn literal_endpoint(
58 literal: i32,
59 pick_literal: bool,
60 positive_chains: &[Vec<usize>],
61 negative_chains: &[Vec<usize>],
62 endpoint_step: usize,
63) -> usize {
64 let variable = literal.unsigned_abs() as usize - 1;
65 match (literal > 0, pick_literal) {
66 (true, true) | (false, false) => positive_chains[variable][endpoint_step],
67 (true, false) | (false, true) => negative_chains[variable][endpoint_step],
68 }
69}
70
71fn build_ullman_construction(source: &KSatisfiability<K3>) -> UllmanConstruction {
72 let num_vars = source.num_vars();
73 let num_clauses = source.num_clauses();
74 let time_limit = time_limit(num_vars);
75 let num_processors = processor_upper_bound(num_vars, num_clauses);
76 let capacities = slot_capacities(num_vars, num_clauses);
77
78 let mut next_job = 0usize;
79
80 let mut positive_chains = vec![vec![0; num_vars + 1]; num_vars];
81 let mut negative_chains = vec![vec![0; num_vars + 1]; num_vars];
82 for variable in 0..num_vars {
83 for step in 0..=num_vars {
84 positive_chains[variable][step] = next_job;
85 next_job += 1;
86 negative_chains[variable][step] = next_job;
87 next_job += 1;
88 }
89 }
90
91 let positive_forcing: Vec<usize> = (0..num_vars)
92 .map(|_| {
93 let job = next_job;
94 next_job += 1;
95 job
96 })
97 .collect();
98 let negative_forcing: Vec<usize> = (0..num_vars)
99 .map(|_| {
100 let job = next_job;
101 next_job += 1;
102 job
103 })
104 .collect();
105
106 let mut clause_jobs = vec![vec![0; 7]; num_clauses];
107 for jobs in &mut clause_jobs {
108 for job in jobs {
109 *job = next_job;
110 next_job += 1;
111 }
112 }
113
114 let mut filler_jobs_by_slot = vec![Vec::new(); time_limit];
115 for slot in 0..time_limit {
116 let filler_count = num_processors - capacities[slot];
117 filler_jobs_by_slot[slot] = (0..filler_count)
118 .map(|_| {
119 let job = next_job;
120 next_job += 1;
121 job
122 })
123 .collect();
124 }
125
126 let mut precedences = Vec::new();
127
128 for variable in 0..num_vars {
129 for step in 0..num_vars {
130 precedences.push((
131 positive_chains[variable][step],
132 positive_chains[variable][step + 1],
133 ));
134 precedences.push((
135 negative_chains[variable][step],
136 negative_chains[variable][step + 1],
137 ));
138 }
139 precedences.push((
140 positive_chains[variable][variable],
141 positive_forcing[variable],
142 ));
143 precedences.push((
144 negative_chains[variable][variable],
145 negative_forcing[variable],
146 ));
147 }
148
149 for (clause_index, clause) in source.clauses().iter().enumerate() {
150 for (pattern_index, &clause_job) in clause_jobs[clause_index].iter().enumerate() {
151 let pattern = pattern_index + 1;
152 for position in 0..3 {
153 let literal = clause.literals[position];
154 let bit_is_one = ((pattern >> (2 - position)) & 1) == 1;
155 precedences.push((
156 literal_endpoint(
157 literal,
158 bit_is_one,
159 &positive_chains,
160 &negative_chains,
161 num_vars,
162 ),
163 clause_job,
164 ));
165 }
166 }
167 }
168
169 for slot in 0..time_limit.saturating_sub(1) {
170 for &pred in &filler_jobs_by_slot[slot] {
171 for &succ in &filler_jobs_by_slot[slot + 1] {
172 precedences.push((pred, succ));
173 }
174 }
175 }
176
177 UllmanConstruction {
178 time_limit,
179 num_processors,
180 positive_chains,
181 #[cfg(any(test, feature = "example-db"))]
182 negative_chains,
183 #[cfg(any(test, feature = "example-db"))]
184 positive_forcing,
185 #[cfg(any(test, feature = "example-db"))]
186 negative_forcing,
187 #[cfg(any(test, feature = "example-db"))]
188 clause_jobs,
189 #[cfg(any(test, feature = "example-db"))]
190 filler_jobs_by_slot,
191 precedences,
192 num_jobs: next_job,
193 }
194}
195
196fn task_slot(config: &[usize], task: usize, d_max: usize) -> Option<usize> {
197 let start = task.checked_mul(d_max)?;
198 let end = start.checked_add(d_max)?;
199 let task_slice = config.get(start..end)?;
200 task_slice.iter().position(|&value| value == 1)
201}
202
203#[cfg(any(test, feature = "example-db"))]
204fn set_task_slot(task_slots: &mut [Option<usize>], job: usize, slot: usize) {
205 task_slots[job] = Some(slot);
206}
207
208#[cfg(any(test, feature = "example-db"))]
209fn clause_pattern_for_assignment(
210 clause: &crate::models::formula::CNFClause,
211 assignment: &[usize],
212) -> usize {
213 let mut pattern = 0usize;
214 for (position, &literal) in clause.literals.iter().enumerate() {
215 let variable = literal.unsigned_abs() as usize - 1;
216 let value = assignment.get(variable).copied().unwrap_or(0) == 1;
217 let literal_true = if literal > 0 { value } else { !value };
218 if literal_true {
219 pattern |= 1 << (2 - position);
220 }
221 }
222 pattern
223}
224
225#[cfg(any(test, feature = "example-db"))]
226fn construct_schedule_from_assignment(
227 target: &PreemptiveScheduling,
228 assignment: &[usize],
229 source: &KSatisfiability<K3>,
230) -> Option<Vec<usize>> {
231 let construction = build_ullman_construction(source);
232 if assignment.len() != source.num_vars() || target.num_tasks() != construction.num_jobs {
233 return None;
234 }
235
236 let mut task_slots = vec![None; construction.num_jobs];
237
238 for variable in 0..source.num_vars() {
239 let value_is_true = assignment[variable] == 1;
240 for step in 0..=source.num_vars() {
241 if value_is_true {
242 set_task_slot(
243 &mut task_slots,
244 construction.positive_chains[variable][step],
245 step,
246 );
247 set_task_slot(
248 &mut task_slots,
249 construction.negative_chains[variable][step],
250 step + 1,
251 );
252 } else {
253 set_task_slot(
254 &mut task_slots,
255 construction.negative_chains[variable][step],
256 step,
257 );
258 set_task_slot(
259 &mut task_slots,
260 construction.positive_chains[variable][step],
261 step + 1,
262 );
263 }
264 }
265
266 let positive_slot = task_slots[construction.positive_chains[variable][variable]]
267 .expect("positive chain slot is assigned");
268 let negative_slot = task_slots[construction.negative_chains[variable][variable]]
269 .expect("negative chain slot is assigned");
270 set_task_slot(
271 &mut task_slots,
272 construction.positive_forcing[variable],
273 positive_slot + 1,
274 );
275 set_task_slot(
276 &mut task_slots,
277 construction.negative_forcing[variable],
278 negative_slot + 1,
279 );
280 }
281
282 for (clause_index, clause) in source.clauses().iter().enumerate() {
283 let pattern = clause_pattern_for_assignment(clause, assignment);
284 if pattern == 0 {
285 return None;
286 }
287 for pattern_index in 0..7 {
288 let slot = if pattern_index + 1 == pattern {
289 source.num_vars() + 1
290 } else {
291 source.num_vars() + 2
292 };
293 set_task_slot(
294 &mut task_slots,
295 construction.clause_jobs[clause_index][pattern_index],
296 slot,
297 );
298 }
299 }
300
301 for (slot, fillers) in construction.filler_jobs_by_slot.iter().enumerate() {
302 for &job in fillers {
303 set_task_slot(&mut task_slots, job, slot);
304 }
305 }
306
307 let d_max = target.d_max();
308 let mut config = vec![0usize; construction.num_jobs * d_max];
309 for (job, slot) in task_slots.into_iter().enumerate() {
310 let slot = slot?;
311 config[job * d_max + slot] = 1;
312 }
313 Some(config)
314}
315
316#[derive(Debug, Clone)]
318pub struct Reduction3SATToPreemptiveScheduling {
319 target: PreemptiveScheduling,
320 positive_start_jobs: Vec<usize>,
321 threshold: usize,
322}
323
324impl Reduction3SATToPreemptiveScheduling {
325 pub fn threshold(&self) -> usize {
326 self.threshold
327 }
328}
329
330impl ReductionResult for Reduction3SATToPreemptiveScheduling {
331 type Source = KSatisfiability<K3>;
332 type Target = PreemptiveScheduling;
333
334 fn target_problem(&self) -> &Self::Target {
335 &self.target
336 }
337
338 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
339 let d_max = self.target.d_max();
340 self.positive_start_jobs
341 .iter()
342 .map(|&job| usize::from(task_slot(target_solution, job, d_max) == Some(0)))
343 .collect()
344 }
345}
346
347#[reduction(
348 overhead = {
349 num_tasks = "(((2 * num_vars + 2) + 6 * num_clauses + sqrt(((2 * num_vars + 2) - 6 * num_clauses)^2)) / 2) * (num_vars + 3)",
350 num_processors = "((2 * num_vars + 2) + 6 * num_clauses + sqrt(((2 * num_vars + 2) - 6 * num_clauses)^2)) / 2",
351 d_max = "(((2 * num_vars + 2) + 6 * num_clauses + sqrt(((2 * num_vars + 2) - 6 * num_clauses)^2)) / 2) * (num_vars + 3)",
352 }
353)]
354impl ReduceTo<PreemptiveScheduling> for KSatisfiability<K3> {
355 type Result = Reduction3SATToPreemptiveScheduling;
356
357 fn reduce_to(&self) -> Self::Result {
358 let construction = build_ullman_construction(self);
359 let target = PreemptiveScheduling::new(
360 vec![1; construction.num_jobs],
361 construction.num_processors,
362 construction.precedences.clone(),
363 );
364
365 Reduction3SATToPreemptiveScheduling {
366 target,
367 positive_start_jobs: construction
368 .positive_chains
369 .iter()
370 .map(|chain| chain[0])
371 .collect(),
372 threshold: construction.time_limit,
373 }
374 }
375}
376
377#[cfg(feature = "example-db")]
378pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
379 use crate::export::SolutionPair;
380 use crate::models::formula::CNFClause;
381
382 vec![crate::example_db::specs::RuleExampleSpec {
383 id: "ksatisfiability_to_preemptivescheduling",
384 build: || {
385 let source = KSatisfiability::<K3>::new(3, vec![CNFClause::new(vec![1, 2, 3])]);
386 let reduction = ReduceTo::<PreemptiveScheduling>::reduce_to(&source);
387 let source_config = vec![0, 0, 1];
388 let target_config = construct_schedule_from_assignment(
389 reduction.target_problem(),
390 &source_config,
391 &source,
392 )
393 .expect("canonical example assignment should yield a schedule");
394 crate::example_db::specs::rule_example_with_witness::<_, PreemptiveScheduling>(
395 source,
396 SolutionPair {
397 source_config,
398 target_config,
399 },
400 )
401 },
402 }]
403}
404
405#[cfg(test)]
406#[path = "../unit_tests/rules/ksatisfiability_preemptivescheduling.rs"]
407mod tests;