Skip to main content

problemreductions/models/misc/
sequencing_to_minimize_maximum_cumulative_cost.rs

1//! Sequencing to Minimize Maximum Cumulative Cost problem implementation.
2//!
3//! Given a set of tasks with integer costs and precedence constraints, find
4//! a valid one-machine schedule that minimizes the maximum cumulative cost
5//! over all prefixes.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::de::Error as _;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "SequencingToMinimizeMaximumCumulativeCost",
15        display_name: "Sequencing to Minimize Maximum Cumulative Cost",
16        aliases: &[],
17        dimensions: &[],
18        module_path: module_path!(),
19        description: "Schedule tasks with precedence constraints to minimize the maximum cumulative cost prefix",
20        fields: &[
21            FieldInfo { name: "costs", type_name: "Vec<i64>", description: "Task costs in schedule order-independent indexing" },
22            FieldInfo { name: "precedences", type_name: "Vec<(usize, usize)>", description: "Precedence pairs (predecessor, successor)" },
23        ],
24    }
25}
26
27/// Sequencing to Minimize Maximum Cumulative Cost.
28///
29/// Given a set of tasks `T`, a cost `c(t) in Z` for each task, and a partial
30/// order on the tasks, find a schedule that respects the precedences and
31/// minimizes the maximum cumulative cost over all prefixes.
32///
33/// # Representation
34///
35/// Configurations use Lehmer-code dimensions `[n, n-1, ..., 1]` to encode a
36/// permutation of the task indices.
37#[derive(Debug, Clone, Serialize)]
38pub struct SequencingToMinimizeMaximumCumulativeCost {
39    costs: Vec<i64>,
40    precedences: Vec<(usize, usize)>,
41}
42
43#[derive(Debug, Deserialize)]
44struct SequencingToMinimizeMaximumCumulativeCostUnchecked {
45    costs: Vec<i64>,
46    precedences: Vec<(usize, usize)>,
47}
48
49impl SequencingToMinimizeMaximumCumulativeCost {
50    /// Create a new instance.
51    ///
52    /// # Panics
53    ///
54    /// Panics if any precedence endpoint is out of range.
55    pub fn new(costs: Vec<i64>, precedences: Vec<(usize, usize)>) -> Self {
56        validate_precedences(&precedences, costs.len());
57        Self { costs, precedences }
58    }
59
60    /// Return the task costs.
61    pub fn costs(&self) -> &[i64] {
62        &self.costs
63    }
64
65    /// Return the precedence constraints.
66    pub fn precedences(&self) -> &[(usize, usize)] {
67        &self.precedences
68    }
69
70    /// Return the number of tasks.
71    pub fn num_tasks(&self) -> usize {
72        self.costs.len()
73    }
74
75    /// Return the number of precedence constraints.
76    pub fn num_precedences(&self) -> usize {
77        self.precedences.len()
78    }
79
80    fn decode_schedule(&self, config: &[usize]) -> Option<Vec<usize>> {
81        super::decode_lehmer(config, self.num_tasks())
82    }
83}
84
85impl<'de> Deserialize<'de> for SequencingToMinimizeMaximumCumulativeCost {
86    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87    where
88        D: serde::Deserializer<'de>,
89    {
90        let unchecked =
91            SequencingToMinimizeMaximumCumulativeCostUnchecked::deserialize(deserializer)?;
92        if let Some(message) =
93            precedence_validation_error(&unchecked.precedences, unchecked.costs.len())
94        {
95            return Err(D::Error::custom(message));
96        }
97        Ok(Self {
98            costs: unchecked.costs,
99            precedences: unchecked.precedences,
100        })
101    }
102}
103
104fn validate_precedences(precedences: &[(usize, usize)], num_tasks: usize) {
105    if let Some(message) = precedence_validation_error(precedences, num_tasks) {
106        panic!("{message}");
107    }
108}
109
110fn precedence_validation_error(precedences: &[(usize, usize)], num_tasks: usize) -> Option<String> {
111    for &(pred, succ) in precedences {
112        if pred >= num_tasks {
113            return Some(format!(
114                "predecessor index {} out of range (num_tasks = {})",
115                pred, num_tasks
116            ));
117        }
118        if succ >= num_tasks {
119            return Some(format!(
120                "successor index {} out of range (num_tasks = {})",
121                succ, num_tasks
122            ));
123        }
124    }
125    None
126}
127
128impl Problem for SequencingToMinimizeMaximumCumulativeCost {
129    const NAME: &'static str = "SequencingToMinimizeMaximumCumulativeCost";
130    type Value = crate::types::Min<i64>;
131
132    fn variant() -> Vec<(&'static str, &'static str)> {
133        crate::variant_params![]
134    }
135
136    fn dims(&self) -> Vec<usize> {
137        super::lehmer_dims(self.num_tasks())
138    }
139
140    fn evaluate(&self, config: &[usize]) -> crate::types::Min<i64> {
141        let Some(schedule) = self.decode_schedule(config) else {
142            return crate::types::Min(None);
143        };
144
145        let mut positions = vec![0usize; self.num_tasks()];
146        for (position, &task) in schedule.iter().enumerate() {
147            positions[task] = position;
148        }
149        for &(pred, succ) in &self.precedences {
150            if positions[pred] >= positions[succ] {
151                return crate::types::Min(None);
152            }
153        }
154
155        let mut cumulative = 0i64;
156        let mut max_cumulative = 0i64;
157        for &task in &schedule {
158            cumulative += self.costs[task];
159            if cumulative > max_cumulative {
160                max_cumulative = cumulative;
161            }
162        }
163        crate::types::Min(Some(max_cumulative))
164    }
165}
166
167crate::declare_variants! {
168    default SequencingToMinimizeMaximumCumulativeCost => "factorial(num_tasks)",
169}
170
171#[cfg(feature = "example-db")]
172pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
173    vec![crate::example_db::specs::ModelExampleSpec {
174        id: "sequencing_to_minimize_maximum_cumulative_cost",
175        instance: Box::new(SequencingToMinimizeMaximumCumulativeCost::new(
176            vec![2, -1, 3, -2, 1, -3],
177            vec![(0, 2), (1, 2), (1, 3), (2, 4), (3, 5), (4, 5)],
178        )),
179        optimal_config: vec![1, 0, 1, 0, 0, 0],
180        optimal_value: serde_json::json!(3),
181    }]
182}
183
184#[cfg(test)]
185#[path = "../../unit_tests/models/misc/sequencing_to_minimize_maximum_cumulative_cost.rs"]
186mod tests;