rustsat_tools/encodings/
assignment.rs1use std::io;
7
8#[derive(Clone, PartialEq, Eq)]
10pub struct Assignment {
11 pub(crate) n_tasks: usize,
12 pub(crate) n_objs: usize,
13 pub(crate) costs: Vec<usize>,
14}
15
16impl Assignment {
17 fn empty(n_tasks: usize, n_objs: usize) -> Self {
18 Self {
19 n_tasks,
20 n_objs,
21 costs: vec![0; n_objs * n_tasks * n_tasks],
22 }
23 }
24
25 pub fn from_file(reader: impl io::BufRead) -> anyhow::Result<Self> {
26 parsing::parse_moolib(reader)
27 }
28
29 pub fn idx(&self, task: usize, agent: usize, obj: usize) -> usize {
30 debug_assert!(task < self.n_tasks);
31 debug_assert!(agent < self.n_tasks);
32 debug_assert!(obj < self.n_objs);
33 obj * self.n_tasks * self.n_tasks + task * self.n_tasks + agent
34 }
35
36 pub fn cost(&self, task: usize, agent: usize, obj: usize) -> usize {
37 self.costs[self.idx(task, agent, obj)]
38 }
39
40 pub fn cost_mut(&mut self, task: usize, agent: usize, obj: usize) -> &mut usize {
41 let idx = self.idx(task, agent, obj);
42 self.costs.get_mut(idx).unwrap()
43 }
44}
45
46mod parsing {
47 use std::io;
48
49 use anyhow::Context;
50 use nom::character::complete::u32;
51
52 use crate::parsing::{callback_list, single_value};
53
54 macro_rules! next_non_comment_line {
55 ($reader:expr) => {
56 'block: {
57 let mut buf = String::new();
58 while $reader.read_line(&mut buf)? > 0 {
59 if !buf.trim_start().starts_with('#') && !buf.trim().is_empty() {
60 break 'block Some(buf);
61 }
62 buf.clear();
63 }
64 None
65 }
66 };
67 }
68
69 pub fn parse_moolib(mut reader: impl io::BufRead) -> anyhow::Result<super::Assignment> {
70 let line = next_non_comment_line!(reader)
71 .context("file ended before number of objectives line")?;
72 let (_, n_objs) = single_value(u32, "#")(&line)
73 .map_err(|e| e.to_owned())
74 .with_context(|| format!("failed to parse number of objectives line '{line}'"))?;
75 let line =
76 next_non_comment_line!(reader).context("file ended before number of tasks line")?;
77 let (_, n_tasks) = single_value(u32, "#")(&line)
78 .map_err(|e| e.to_owned())
79 .with_context(|| format!("failed to parse number of tasks line '{line}'"))?;
80 let mut inst = super::Assignment::empty(n_tasks as usize, n_objs as usize);
81 let mut buf = String::new();
82 let mut depth = 0;
83 let mut obj_idx = 0;
84 let mut task_idx = 0;
85 while reader.read_line(&mut buf)? > 0 {
86 let mut remain = buf.trim_start();
87 loop {
88 if remain.starts_with('[') && depth < 2 {
89 depth += 1;
90 remain = &remain[1..];
91 }
92 if remain.starts_with(']') {
93 depth -= 1;
94 if depth == 1 {
95 task_idx = 0;
96 }
97 remain = &remain[1..];
98 }
99 if remain.starts_with(',') {
100 match depth {
101 1 => obj_idx += 1,
102 2 => task_idx += 1,
103 _ => unreachable!(),
104 };
105 remain = &remain[1..];
106 }
107 if remain.starts_with('#') || remain.trim().is_empty() {
108 break;
109 }
110 if depth == 2 {
111 let mut agent_idx = 0;
112 remain = callback_list(remain.trim_start(), u32, |value| {
113 anyhow::ensure!(obj_idx < n_objs, "too many objectives");
114 anyhow::ensure!(task_idx < n_tasks, "too many tasks");
115 anyhow::ensure!(agent_idx < n_tasks, "too many agents");
116 *inst.cost_mut(task_idx as usize, agent_idx as usize, obj_idx as usize) =
117 value as usize;
118 agent_idx += 1;
119 Ok(())
120 })?;
121 }
122 }
123 buf.clear();
124 }
125 anyhow::ensure!(
126 obj_idx + 1 == n_objs,
127 "number of objectives does not match up"
128 );
129 Ok(inst)
130 }
131
132 #[cfg(test)]
133 mod tests {
134 use std::{fs::File, io::BufReader};
135
136 #[test]
137 fn moolib() {
138 let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
139 let reader = BufReader::new(
140 File::open(format!("{manifest}/data/AP_p-3_n-5_ins-1.dat")).unwrap(),
141 );
142 super::parse_moolib(reader).unwrap();
143 }
144 }
145}