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