1use crate::{Buildable, Builder, IndexDigraph};
21use std::collections::HashMap;
22use std::error;
23use std::fmt;
24use std::io::{self, BufRead, BufReader, Read};
25
26#[derive(Debug)]
28pub enum Error {
29 Io(io::Error),
30 Format { line: usize, msg: String },
31 Data { line: usize, msg: String },
32}
33
34impl From<io::Error> for Error {
35 fn from(err: io::Error) -> Self {
36 Error::Io(err)
37 }
38}
39
40impl fmt::Display for Error {
41 fn fmt(&self, fmt: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
42 use self::Error::*;
43 match self {
44 Io(err) => err.fmt(fmt),
45 Format { line, msg } => write!(fmt, "Format error on line {}: {}", line, msg),
46 Data { line, msg } => write!(fmt, "Data error on line {}: {}", line, msg),
47 }
48 }
49}
50
51impl error::Error for Error {
52 fn cause(&self) -> Option<&dyn error::Error> {
53 match self {
54 Error::Io(err) => Some(err),
55 _ => None,
56 }
57 }
58}
59
60pub type Result<T> = std::result::Result<T, Error>;
61
62pub struct Instance<G> {
64 pub name: Option<String>,
66 pub graph: G,
68 pub balances: Vec<f64>,
70 pub lower: Vec<f64>,
72 pub upper: Vec<f64>,
74 pub costs: Vec<f64>,
76 pub node_names: Vec<String>,
80 pub edge_names: Vec<String>,
84}
85
86fn find_token(line: &str, beg: usize, end: usize) -> &str {
87 if end <= line.len() {
88 line[beg..end].trim()
89 } else if beg < line.len() {
90 line[beg..].trim()
91 } else {
92 &line[0..0]
93 }
94}
95
96struct Reader<R: BufRead> {
97 line: usize,
98 buf: String,
99 io: R,
100}
101
102impl<R: BufRead> Reader<R> {
103 fn next_line(&mut self) -> Result<bool> {
107 loop {
108 self.line += 1;
109 self.buf.clear();
110 if self.io.read_line(&mut self.buf)? == 0 {
111 return Ok(false);
112 }
113
114 if !self.buf.trim().is_empty() && !self.buf.starts_with('*') {
116 return Ok(true);
117 }
118 }
119 }
120
121 fn next_field_line(&mut self) -> Result<bool> {
127 if self.next_line()? {
128 Ok(!self.is_section_header())
129 } else {
130 Err(self.fmt_error("Unexpected end of file (require ENDATA)".to_string()))
131 }
132 }
133
134 fn is_section_header(&self) -> bool {
138 !self.buf.starts_with(' ')
139 }
140
141 fn field(&self, i: usize) -> &str {
143 match i {
144 0 => {
145 assert!(self.is_section_header());
146 find_token(&self.buf, 0, 14)
147 }
148 2 => find_token(&self.buf, 1, 3),
149 5 => find_token(&self.buf, 4, 14),
150 15 => find_token(&self.buf, 14, 24),
151 25 => find_token(&self.buf, 24, 39),
152 40 => find_token(&self.buf, 39, 49),
153 50 => find_token(&self.buf, 49, self.buf.len().max(50)),
154 _ => unimplemented!("Invalid field number"),
155 }
156 }
157
158 fn non_empty_field(&self, i: usize) -> Result<&str> {
160 let f = self.field(i);
161 if !f.is_empty() {
162 Ok(f)
163 } else {
164 Err(self.fmt_error(format!("Expected non-empty field in column {}", i)))
165 }
166 }
167
168 fn ensure_is_empty(&self, i: usize) -> Result<()> {
170 if let 1 | 2 | 5 | 15 | 25 | 40 | 50 = i {
171 if i > self.buf.len() || self.buf[i..].trim().is_empty() {
172 Ok(())
173 } else {
174 Err(self.fmt_error(format!("Unexpected data after column {}", i)))
175 }
176 } else {
177 unimplemented!("Invalid field number")
178 }
179 }
180
181 fn fmt_error(&self, msg: String) -> Error {
183 Error::Format { line: self.line, msg }
184 }
185
186 fn data_error(&self, msg: String) -> Error {
188 Error::Data { line: self.line, msg }
189 }
190}
191
192struct NodeInfo<N> {
193 node: N,
194 balance: Option<f64>,
195}
196
197impl<N> NodeInfo<N> {
198 fn new(u: N) -> NodeInfo<N> {
199 NodeInfo { node: u, balance: None }
200 }
201}
202
203struct EdgeInfo<N> {
204 cost: Option<f64>,
205 src: Option<N>,
206 snk: Option<N>,
207 lb: Option<f64>,
208 ub: Option<f64>,
209}
210
211impl<N> EdgeInfo<N> {
212 fn new() -> EdgeInfo<N> {
213 EdgeInfo {
214 cost: None,
215 src: None,
216 snk: None,
217 lb: None,
218 ub: None,
219 }
220 }
221}
222
223fn add_edge<R: BufRead, N: Copy>(
224 reader: &Reader<R>,
225 col: &str,
226 row: &str,
227 coeff: &str,
228 e: &mut EdgeInfo<N>,
229 obj_row: &Option<String>,
230 rows: &HashMap<String, NodeInfo<N>>,
231) -> Result<()> {
232 let coeff = coeff
233 .parse::<f64>()
234 .map_err(|err| reader.fmt_error(format!("Invalid coefficient: {}", err)))?;
235
236 if obj_row.as_ref().map(|obj| obj == row).unwrap_or(false) {
237 if e.cost.is_some() {
239 return Err(reader.data_error(format!("Multiple cost coefficients in column {}", col)));
240 }
241 e.cost = Some(coeff)
242 } else {
243 let u = rows
244 .get(row)
245 .ok_or_else(|| reader.data_error(format!("Unknown row: {}", row)))?;
246
247 #[allow(clippy::float_cmp)]
248 if coeff == 1.0 {
249 if e.src.is_some() {
251 return Err(reader.data_error(format!("Multiple +1 coefficients in column {}", col)));
252 }
253 e.src = Some(u.node);
254 } else if coeff == -1.0 {
255 if e.snk.is_some() {
256 return Err(reader.data_error(format!("Multiple -1 coefficients in column {}", col)));
257 }
258 e.snk = Some(u.node);
259 } else {
260 return Err(reader.data_error(format!("Only coefficients +1 and -1 are supported (got: {})", coeff)));
261 }
262 }
263
264 Ok(())
265}
266
267fn add_rhs<R: BufRead, N: Copy>(
268 reader: &Reader<R>,
269 row: &str,
270 rhs: &str,
271 rows: &mut HashMap<String, NodeInfo<N>>,
272) -> Result<()> {
273 let rhs = rhs
274 .parse::<f64>()
275 .map_err(|err| reader.fmt_error(format!("Invalid right-hand side: {}", err)))?;
276
277 let u = rows
278 .get_mut(row)
279 .ok_or_else(|| reader.data_error(format!("Unknown row: {}", row)))?;
280
281 if u.balance.is_some() {
282 return Err(reader.data_error(format!("Multiple right-hand side values in row {}", row)));
283 }
284 u.balance = Some(rhs);
285
286 Ok(())
287}
288
289pub fn read<G, R: Read>(reader: R) -> Result<Instance<G>>
291where
292 G: IndexDigraph + Buildable,
293{
294 let mut reader = Reader {
295 io: BufReader::new(reader),
296 line: 0,
297 buf: String::new(),
298 };
299
300 let mut name = None;
301 let mut minimize = None;
302 let mut obj_row = None;
303 let mut cols = HashMap::new(); let mut rows = HashMap::new(); let mut rhsname = None;
306 let mut bndname = None;
307 let mut graph = G::new_builder();
308
309 if !reader.next_line()? {
310 return Err(reader.fmt_error("Empty file".to_string()));
311 }
312
313 loop {
314 if !reader.is_section_header() {
315 return Err(reader.fmt_error("Expected a section header (non-whitespace in column 0)".to_string()));
316 }
317 let section = reader.field(0);
318 match section {
319 "NAME" => {
320 let n = reader.field(15);
321 if !n.is_empty() {
322 name = Some(n.to_string());
323 }
324 if reader.next_field_line()? {
325 return Err(reader.fmt_error("Invalid record in NAME section".to_string()));
326 }
327 }
328 "ROWS" => {
329 while reader.next_field_line()? {
330 match reader.field(2) {
331 "N" => {
332 if obj_row.is_some() {
333 return Err(reader.fmt_error("Only one objective row is supported".to_string()));
334 }
335 obj_row = Some(reader.non_empty_field(5)?.to_string());
336 }
337 "E" => {
338 let row = reader.non_empty_field(5)?;
339 reader.ensure_is_empty(15)?;
340 if rows.insert(row.to_string(), NodeInfo::new(graph.add_node())).is_some() {
341 return Err(reader.data_error(format!("Row {} specified multiple times", row)));
342 }
343 }
344 "L" | "G" => {
345 return Err(reader.fmt_error("Only equality constraints are supported".to_string()))
346 }
347 _ => {
348 return Err(reader
349 .fmt_error(format!("Invalid row type {} (expected N, L, E or G)", reader.field(2))))
350 }
351 }
352 }
353 }
354 "COLUMNS" => {
355 while reader.next_field_line()? {
356 if !reader.field(2).is_empty() {
357 return Err(reader.fmt_error("Unexpected field in column 2".to_string()));
358 }
359
360 let col = reader.non_empty_field(5)?; let e = cols.entry(col.to_string()).or_insert_with(EdgeInfo::new);
363
364 let row1 = reader.non_empty_field(15)?; let coeff1 = reader.non_empty_field(25)?; add_edge(&reader, col, row1, coeff1, e, &obj_row, &rows)?;
368
369 let row2 = reader.field(40);
370 let coeff2 = reader.field(50);
371 if row2.is_empty() != coeff2.is_empty() {
372 if row2.is_empty() {
373 return Err(reader.fmt_error("Second row name missing".to_string()));
374 } else {
375 return Err(reader.fmt_error("Second coefficient missing".to_string()));
376 }
377 }
378
379 if !row2.is_empty() {
380 add_edge(&reader, col, row2, coeff2, e, &obj_row, &rows)?;
381 }
382 }
383 }
384 "RHS" => {
385 while reader.next_field_line()? {
386 if !reader.field(2).is_empty() {
387 return Err(reader.fmt_error("Unexpected field in column 2".to_string()));
388 }
389
390 if let Some(rhsname) = rhsname.as_ref() {
391 if rhsname != reader.non_empty_field(5)? {
392 return Err(reader.data_error("Only one right-hand side is suported".to_string()));
393 }
394 } else {
395 rhsname = Some(reader.non_empty_field(5)?.to_string());
396 }
397
398 let row1 = reader.non_empty_field(15)?;
399 let rhs1 = reader.non_empty_field(25)?;
400 add_rhs(&reader, row1, rhs1, &mut rows)?;
401
402 let row2 = reader.field(40);
403 let rhs2 = reader.field(50);
404
405 if row2.is_empty() != rhs2.is_empty() {
406 if row2.is_empty() {
407 return Err(reader.fmt_error("Second row name missing".to_string()));
408 } else {
409 return Err(reader.fmt_error("Second right-hand side value missing".to_string()));
410 }
411 }
412 if !row2.is_empty() {
413 add_rhs(&reader, row2, rhs2, &mut rows)?;
414 }
415 }
416 }
417 "BOUNDS" => {
418 while reader.next_field_line()? {
419 if let Some(bndname) = bndname.as_ref() {
420 if bndname != reader.non_empty_field(5)? {
421 return Err(reader.data_error("Only one bound name is suported".to_string()));
422 }
423 } else {
424 bndname = Some(reader.non_empty_field(5)?.to_string());
425 }
426
427 let typ = reader.non_empty_field(2)?;
428 let (lb, ub) = match typ {
429 "FR" => {
430 reader.ensure_is_empty(25)?;
431 (-f64::INFINITY, f64::INFINITY)
432 }
433 "MI" => {
434 reader.ensure_is_empty(25)?;
435 (-f64::INFINITY, 0.0)
436 }
437 _ => {
438 reader.ensure_is_empty(40)?;
439 let val = reader
440 .non_empty_field(25)?
441 .parse::<f64>()
442 .map_err(|err| reader.fmt_error(format!("Invalid bound: {}", err)))?;
443 (val, val)
444 }
445 };
446
447 let col = reader.non_empty_field(15)?;
448 let e = cols
449 .get_mut(col)
450 .ok_or_else(|| reader.data_error(format!("Unknown column: {}", col)))?;
451
452 if let "UP" | "FX" | "MI" | "FR" = typ {
453 if e.ub.is_some() {
454 return Err(reader.data_error(format!("Multiple upper bounds for {}", col)));
455 }
456 e.ub = Some(ub);
457 }
458 if let "LO" | "FX" | "FR" = typ {
459 if e.lb.is_some() {
460 return Err(reader.data_error(format!("Multiple lower bounds for {}", col)));
461 }
462 e.lb = Some(lb);
463 }
464 }
465 }
466 "RANGES" => {
467 if reader.next_field_line()? {
468 return Err(reader.data_error("RANGES section is not supported".to_string()));
469 }
470 }
471 "OBJSENSE" => {
472 while reader.next_field_line()? {
473 if minimize.is_some() {
474 return Err(reader.fmt_error("OBJSENSE must be specified at most once".to_string()));
475 }
476 match reader.field(5) {
477 "MIN" => minimize = Some(true),
478 "MAX" => minimize = Some(false),
479 _ => {
480 return Err(reader.fmt_error(format!(
481 "Invalid objective sense {} (must be 'MIN' or 'MAX')",
482 reader.field(5)
483 )))
484 }
485 }
486 reader.ensure_is_empty(15)?;
487 }
488 }
489 "ENDATA" => {
490 if reader.next_line()? {
491 return Err(reader.fmt_error("Unexpected line after ENDATA".to_string()));
492 }
493 break;
494 }
495 _ => {
496 return Err(reader.fmt_error(format!("Unknown section: {}", section)));
497 }
498 }
499 }
500
501 let n = rows.len();
503 let m = cols.len();
504
505 let mut node_names = vec![String::new(); n];
506 let mut balances = vec![0.0; n];
507 let mut edge_names = Vec::with_capacity(m);
508 let mut lower = Vec::with_capacity(m);
509 let mut upper = Vec::with_capacity(m);
510 let mut costs = Vec::with_capacity(m);
511
512 for (uname, u) in rows {
513 node_names[graph.node2id(u.node)] = uname;
514 balances[graph.node2id(u.node)] = u.balance.unwrap_or(0.0);
515 }
516
517 for (ename, e) in cols {
518 let u = e
519 .src
520 .ok_or_else(|| reader.data_error(format!("Missing +1 coefficient for edge {}", ename)))?;
521 let v = e
522 .snk
523 .ok_or_else(|| reader.data_error(format!("Missing -1 coefficient for edge {}", ename)))?;
524 graph.add_edge(u, v);
525 edge_names.push(ename);
526 lower.push(e.lb.unwrap_or(0.0));
527 upper.push(e.ub.unwrap_or(f64::INFINITY));
528 costs.push(e.cost.unwrap_or(0.0));
529 }
530 let graph = graph.into_graph();
531
532 if !minimize.unwrap_or(true) {
533 for c in &mut costs {
534 *c = -*c;
535 }
536 }
537
538 Ok(Instance {
539 name,
540 graph,
541 balances,
542 lower,
543 upper,
544 costs,
545 node_names,
546 edge_names,
547 })
548}
549
550pub fn read_from_file<G>(filename: &str) -> Result<Instance<G>>
551where
552 G: IndexDigraph + Buildable,
553{
554 read(std::fs::File::open(filename)?)
555}