Skip to main content

gridgraph_rs/
lib.rs

1//! GRIDGRAPH, a min-cost flow grid graph generator
2//!
3//! Produces a min-cost flow problem instance in DIMACS `.min` format,
4//! laid out on a directed grid graph. Originally by Mauricio G.C. Resende
5//! (1991), AT&T Bell Laboratories.
6//!
7//! This is a Rust rewrite of the [original](http://archive.dimacs.rutgers.edu/pub/netflow/generators/network/gridgraph/)
8//! Fortran program `ggraph1.f`.
9//!
10//! # Usage
11//!
12//! ## Quick start
13//!
14//! ```rust
15//! use gridgraph_rs::{generate_instance, GridGraphParams};
16//!
17//! let params = GridGraphParams::new(3, 3, 100, 10, 12345).unwrap();
18//! let instance = generate_instance(params);
19//! println!("{}", instance.to_dimacs_string());
20//! ```
21//!
22//! ## Writing to a file
23//!
24//! Use [`DimacsInstance::write_dimacs_io`] to stream into any `io::Write` or
25//! the convenience [`DimacsInstance::write_to_file`] helper for buffered file
26//! output.
27//!
28//! ```rust
29//! use gridgraph_rs::{generate_instance, GridGraphParams};
30//! use std::error::Error;
31//! use std::fs::File;
32//! use std::io::{BufWriter, Write};
33//!
34//! fn main() -> Result<(), Box<dyn Error>> {
35//!     let params = GridGraphParams::new(3, 3, 100, 10, 12345)?;
36//!     let instance = generate_instance(params);
37//!     let path = std::env::temp_dir().join("gridgraph_doc_example.min");
38//!
39//!     // Write to any `io::Write` implementor
40//!     {
41//!         let file = File::create(&path)?;
42//!         let mut buf = BufWriter::new(file);
43//!         instance.write_dimacs_io(&mut buf)?;
44//!         buf.flush()?;
45//!     }
46//!
47//!     // Or let the helper create the buffered file for you
48//!     instance.write_to_file(&path)?;
49//!     std::fs::remove_file(path)?;
50//!     Ok(())
51//! }
52//! ```
53//!
54//! The five integers correspond to grid dimensions, capacity/cost bounds
55//! and RNG seed. See [`GridGraphParams`] for details.
56
57use std::collections::VecDeque;
58use std::fmt;
59use std::io::{self, Write};
60
61// ---------------------------------------------------------------------------
62// Public API
63// ---------------------------------------------------------------------------
64
65/// Parameters that fully describe a GRIDGRAPH instance.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub struct GridGraphParams {
68    /// Grid height (rows), must be positive.
69    pub height: i32,
70    /// Grid width (columns), must be at least 2.
71    pub width: i32,
72    /// Maximum arc capacity, sampled uniformly from `[1, max_capacity]`.
73    pub max_capacity: i32,
74    /// Maximum arc cost, sampled uniformly from `[1, max_cost]`.
75    pub max_cost: i32,
76    /// Positive integer seed for the Park-Miller RNG.
77    pub seed: i32,
78}
79
80impl GridGraphParams {
81    /// Validate and build a new parameter set.
82    pub fn new(
83        height: i32,
84        width: i32,
85        max_capacity: i32,
86        max_cost: i32,
87        seed: i32,
88    ) -> Result<Self, GridGraphError> {
89        if height <= 0 {
90            return Err(GridGraphError::InvalidHeight);
91        }
92        if width < 2 {
93            return Err(GridGraphError::InvalidWidth);
94        }
95        if max_capacity <= 0 {
96            return Err(GridGraphError::InvalidMaxCapacity);
97        }
98        if max_cost <= 0 {
99            return Err(GridGraphError::InvalidMaxCost);
100        }
101        if seed <= 0 {
102            return Err(GridGraphError::InvalidSeed);
103        }
104        Ok(Self {
105            height,
106            width,
107            max_capacity,
108            max_cost,
109            seed,
110        })
111    }
112}
113
114/// Errors returned when the supplied parameters are invalid.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum GridGraphError {
117    InvalidHeight,
118    InvalidWidth,
119    InvalidMaxCapacity,
120    InvalidMaxCost,
121    InvalidSeed,
122}
123
124impl fmt::Display for GridGraphError {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        use GridGraphError::*;
127        match self {
128            InvalidHeight => write!(f, "height must be positive"),
129            InvalidWidth => write!(f, "width must be >= 2"),
130            InvalidMaxCapacity => write!(f, "max_capacity must be positive"),
131            InvalidMaxCost => write!(f, "max_cost must be positive"),
132            InvalidSeed => write!(f, "seed must be positive"),
133        }
134    }
135}
136
137impl std::error::Error for GridGraphError {}
138
139/// Directed arc within the generated grid graph.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub struct GridArc {
142    /// Tail node identifier (1-indexed for grid nodes).
143    pub from: i32,
144    /// Head node identifier.
145    pub to: i32,
146    /// Arc capacity.
147    pub capacity: i32,
148    /// Arc cost.
149    pub cost: i32,
150}
151
152/// Complete DIMACS min-cost flow instance for a GRIDGRAPH.
153#[derive(Debug, Clone)]
154pub struct DimacsInstance {
155    params: GridGraphParams,
156    arcs: Vec<GridArc>,
157    node_count: i32,
158    arc_count: i32,
159    source: i32,
160    sink: i32,
161    max_flow: i64,
162}
163
164impl DimacsInstance {
165    /// Original parameters used to generate this instance.
166    pub fn params(&self) -> GridGraphParams {
167        self.params
168    }
169
170    /// Total number of nodes (`H * W + 2`).
171    pub fn node_count(&self) -> i32 {
172        self.node_count
173    }
174
175    /// Total number of arcs.
176    pub fn arc_count(&self) -> i32 {
177        self.arc_count
178    }
179
180    /// Source node identifier (`H * W + 1`).
181    pub fn source(&self) -> i32 {
182        self.source
183    }
184
185    /// Sink node identifier (`H * W + 2`).
186    pub fn sink(&self) -> i32 {
187        self.sink
188    }
189
190    /// Maximum s-t flow value used for supply/demand.
191    pub fn max_flow(&self) -> i64 {
192        self.max_flow
193    }
194
195    /// Borrow the arc list in Fortran order.
196    pub fn arcs(&self) -> &[GridArc] {
197        &self.arcs
198    }
199
200    /// Render the DIMACS `.min` output to a writer.
201    pub fn write_dimacs<W: fmt::Write>(&self, mut writer: W) -> fmt::Result {
202        let params = self.params;
203        writeln!(
204            writer,
205            "c    max-flow min-cost on a{h:3} by {w:3} grid",
206            h = params.height,
207            w = params.width
208        )?;
209        writeln!(
210            writer,
211            "c    cap: [0,{cap:8}]   cost: [0,{cost:8}]   seed:{seed:10}",
212            cap = params.max_capacity,
213            cost = params.max_cost,
214            seed = params.seed
215        )?;
216        writeln!(
217            writer,
218            "p min {nodes:15}{arcs:15}",
219            nodes = self.node_count,
220            arcs = self.arc_count
221        )?;
222        writeln!(
223            writer,
224            "n {src:15}{flow:15}",
225            src = self.source,
226            flow = self.max_flow
227        )?;
228        writeln!(
229            writer,
230            "n {sink:15}{neg_flow:15}",
231            sink = self.sink,
232            neg_flow = -self.max_flow
233        )?;
234        for arc in &self.arcs {
235            writeln!(
236                writer,
237                "a {from:10}{to:10} 0 {cap:10}{cost:10}",
238                from = arc.from,
239                to = arc.to,
240                cap = arc.capacity,
241                cost = arc.cost
242            )?;
243        }
244        Ok(())
245    }
246
247    /// Convenience method returning the formatted DIMACS string.
248    pub fn to_dimacs_string(&self) -> String {
249        let mut out = String::new();
250        self.write_dimacs(&mut out).unwrap();
251        out
252    }
253
254    /// Stream the DIMACS output into any [`io::Write`] implementor.
255    pub fn write_dimacs_io<W: io::Write>(&self, mut writer: W) -> io::Result<()> {
256        struct Adapter<'a, W: io::Write>(&'a mut W);
257
258        impl<'a, W: io::Write> fmt::Write for Adapter<'a, W> {
259            fn write_str(&mut self, s: &str) -> fmt::Result {
260                self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
261            }
262        }
263
264        let mut adapter = Adapter(&mut writer);
265        self.write_dimacs(&mut adapter)
266            .map_err(|_| std::io::Error::other("failed to format DIMACS output"))
267    }
268
269    /// Convenience helper that writes the DIMACS output to a buffered file on disk.
270    pub fn write_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> io::Result<()> {
271        let file = std::fs::File::create(path)?;
272        let mut buf = io::BufWriter::new(file);
273        self.write_dimacs_io(&mut buf)?;
274        buf.flush()
275    }
276}
277
278/// Generate a grid graph instance along with all derived metadata.
279pub fn generate_instance(params: GridGraphParams) -> DimacsInstance {
280    let h = params.height;
281    let w = params.width;
282    let maxcap = params.max_capacity;
283    let maxcost = params.max_cost;
284
285    let nnodes = h * w + 2;
286    let narcs = 2 * (h - 1) * w + w + h;
287    let source = h * w + 1;
288    let sink = h * w + 2;
289
290    let mut seed = params.seed;
291    let arcs = generate_arcs(h, w, maxcap, maxcost, &mut seed);
292
293    // Build flow graph for Dinic's algorithm
294    let mut fg = FlowGraph::new(nnodes as usize + 1, narcs as usize * 2);
295    for arc in &arcs {
296        fg.add_edge(arc.from as usize, arc.to as usize, arc.capacity as i64);
297    }
298    let max_flow = fg.max_flow(source as usize, sink as usize);
299
300    DimacsInstance {
301        params,
302        arcs,
303        node_count: nnodes,
304        arc_count: narcs,
305        source,
306        sink,
307        max_flow,
308    }
309}
310
311/// Generate a DIMACS `.min` string for the supplied parameters.
312pub fn generate_dimacs(params: GridGraphParams) -> String {
313    generate_instance(params).to_dimacs_string()
314}
315
316// ---------------------------------------------------------------------------
317// RNG — Schrage's Park-Miller LCG
318//
319// Uses f32 arithmetic throughout to match Fortran's 32-bit REAL.
320// Reference: L. Schrage, ACM TOMS, 1979.
321// ---------------------------------------------------------------------------
322
323const A: i32 = 16807;
324const P: i32 = 2_147_483_647; // 2^31 - 1
325const B15: i32 = 32768; // 2^15
326const B16: i32 = 65536; // 2^16
327
328/// Advance the Park-Miller LCG state and return a value in [0, 1).
329fn schrage_rand(seed: &mut i32) -> f32 {
330    let ix = *seed;
331    let xhi = ix / B16;
332    let xalo = (ix - xhi * B16) * A;
333    let leftlo = xalo / B16;
334    let fhi = xhi * A + leftlo;
335    let k = fhi / B15;
336    let mut new_ix = ((xalo - leftlo * B16) - P) + (fhi - k * B15) * B16 + k;
337    if new_ix < 0 {
338        new_ix += P;
339    }
340    *seed = new_ix;
341    #[allow(clippy::excessive_precision)]
342    let scale = 4.656_612_875e-10_f32;
343    new_ix as f32 * scale
344}
345
346/// Return a uniform random integer in `[1, max]`.
347fn unif(max: i32, seed: &mut i32) -> i32 {
348    (1.0_f32 + schrage_rand(seed) * (max - 1) as f32) as i32
349}
350
351// ---------------------------------------------------------------------------
352// Arc representation
353// ---------------------------------------------------------------------------
354
355fn node(i: i32, j: i32, w: i32) -> i32 {
356    (i - 1) * w + j
357}
358
359fn generate_arcs(h: i32, w: i32, maxcap: i32, maxcost: i32, seed: &mut i32) -> Vec<GridArc> {
360    let narcs = 2 * (h - 1) * w + w + h;
361    let mut arcs = Vec::with_capacity(narcs as usize);
362
363    let mut caps = vec![0i32; (h + 1) as usize]; // 1-indexed
364    let mut costs = vec![0i32; (h + 1) as usize];
365    let mut capt = vec![0i32; (h + 1) as usize];
366    let mut costt = vec![0i32; (h + 1) as usize];
367
368    // Phase 1 — col 1, rows 1..h-1
369    for i in 1..h {
370        let cap = unif(maxcap, seed);
371        let cost = unif(maxcost, seed);
372        caps[i as usize] = cap;
373        arcs.push(GridArc {
374            from: node(i, 1, w),
375            to: node(i, 2, w),
376            capacity: cap,
377            cost,
378        });
379
380        let cap = unif(maxcap, seed);
381        let cost = unif(maxcost, seed);
382        caps[i as usize] += cap;
383        arcs.push(GridArc {
384            from: node(i, 1, w),
385            to: node(i + 1, 1, w),
386            capacity: cap,
387            cost,
388        });
389
390        costs[i as usize] = unif(maxcost, seed); // extra RNG call
391    }
392
393    // Phase 2 — col 1, row h
394    {
395        let cap = unif(maxcap, seed);
396        let cost = unif(maxcost, seed);
397        caps[h as usize] = cap;
398        arcs.push(GridArc {
399            from: node(h, 1, w),
400            to: node(h, 2, w),
401            capacity: cap,
402            cost,
403        });
404
405        costs[h as usize] = unif(maxcost, seed); // extra RNG call
406    }
407
408    // Phase 3 — interior cols 2..w-2, rows 1..h-1
409    for i in 1..h {
410        for j in 2..=(w - 2) {
411            let cap = unif(maxcap, seed);
412            let cost = unif(maxcost, seed);
413            arcs.push(GridArc {
414                from: node(i, j, w),
415                to: node(i, j + 1, w),
416                capacity: cap,
417                cost,
418            });
419
420            let cap = unif(maxcap, seed);
421            let cost = unif(maxcost, seed);
422            arcs.push(GridArc {
423                from: node(i, j, w),
424                to: node(i + 1, j, w),
425                capacity: cap,
426                cost,
427            });
428        }
429    }
430
431    // Phase 4 — interior cols 2..w-2, row h
432    for j in 2..=(w - 2) {
433        let cap = unif(maxcap, seed);
434        let cost = unif(maxcost, seed);
435        arcs.push(GridArc {
436            from: node(h, j, w),
437            to: node(h, j + 1, w),
438            capacity: cap,
439            cost,
440        });
441    }
442
443    // Phase 5 — col w-1, rows 1..h-1
444    for i in 1..h {
445        let cap = unif(maxcap, seed);
446        let cost = unif(maxcost, seed);
447        capt[i as usize] = cap;
448        arcs.push(GridArc {
449            from: node(i, w - 1, w),
450            to: node(i, w, w),
451            capacity: cap,
452            cost,
453        });
454
455        let cap = unif(maxcap, seed);
456        let cost = unif(maxcost, seed);
457        arcs.push(GridArc {
458            from: node(i, w - 1, w),
459            to: node(i + 1, w - 1, w),
460            capacity: cap,
461            cost,
462        });
463
464        costt[i as usize] = unif(maxcost, seed); // extra RNG call
465    }
466
467    // Phase 6 — col w-1, row h
468    {
469        let cap = unif(maxcap, seed);
470        let cost = unif(maxcost, seed);
471        capt[h as usize] = cap;
472        arcs.push(GridArc {
473            from: node(h, w - 1, w),
474            to: node(h, w, w),
475            capacity: cap,
476            cost,
477        });
478
479        costt[h as usize] = unif(maxcost, seed); // extra RNG call
480    }
481
482    // Phase 7 — col w, rows 1..h-1
483    for i in 1..h {
484        let cap = unif(maxcap, seed);
485        let cost = unif(maxcost, seed);
486        capt[(i + 1) as usize] += cap;
487        arcs.push(GridArc {
488            from: node(i, w, w),
489            to: node(i + 1, w, w),
490            capacity: cap,
491            cost,
492        });
493    }
494
495    let source = h * w + 1;
496    let sink = h * w + 2;
497
498    // Phase 8 — source arcs
499    for i in 1..=h {
500        arcs.push(GridArc {
501            from: source,
502            to: node(i, 1, w),
503            capacity: caps[i as usize],
504            cost: costs[i as usize],
505        });
506    }
507
508    // Phase 9 — sink arcs
509    for i in 1..=h {
510        arcs.push(GridArc {
511            from: node(i, w, w),
512            to: sink,
513            capacity: capt[i as usize],
514            cost: costt[i as usize],
515        });
516    }
517
518    debug_assert_eq!(arcs.len(), narcs as usize);
519    arcs
520}
521
522// ---------------------------------------------------------------------------
523// Dinic's max-flow
524// ---------------------------------------------------------------------------
525
526struct FlowGraph {
527    n: usize,
528    head: Vec<i32>,
529    to: Vec<i32>,
530    cap: Vec<i64>,
531    next: Vec<i32>,
532}
533
534impl FlowGraph {
535    fn new(n: usize, arc_hint: usize) -> Self {
536        Self {
537            n,
538            head: vec![-1; n],
539            to: Vec::with_capacity(arc_hint),
540            cap: Vec::with_capacity(arc_hint),
541            next: Vec::with_capacity(arc_hint),
542        }
543    }
544
545    fn add_edge(&mut self, u: usize, v: usize, c: i64) {
546        let idx = self.to.len() as i32;
547        self.to.push(v as i32);
548        self.cap.push(c);
549        self.next.push(self.head[u]);
550        self.head[u] = idx;
551
552        let idx = self.to.len() as i32;
553        self.to.push(u as i32);
554        self.cap.push(0);
555        self.next.push(self.head[v]);
556        self.head[v] = idx;
557    }
558
559    fn bfs(&self, source: usize, sink: usize, level: &mut [i32]) -> bool {
560        level.iter_mut().for_each(|l| *l = -1);
561        level[source] = 0;
562        let mut queue = VecDeque::new();
563        queue.push_back(source);
564        while let Some(u) = queue.pop_front() {
565            let mut e = self.head[u];
566            while e != -1 {
567                let v = self.to[e as usize] as usize;
568                if level[v] == -1 && self.cap[e as usize] > 0 {
569                    level[v] = level[u] + 1;
570                    queue.push_back(v);
571                }
572                e = self.next[e as usize];
573            }
574        }
575        level[sink] != -1
576    }
577
578    fn dfs(&mut self, u: usize, sink: usize, pushed: i64, level: &[i32], iter: &mut [i32]) -> i64 {
579        if u == sink {
580            return pushed;
581        }
582        while iter[u] != -1 {
583            let e = iter[u] as usize;
584            let v = self.to[e] as usize;
585            if level[v] == level[u] + 1 && self.cap[e] > 0 {
586                let d = self.dfs(v, sink, pushed.min(self.cap[e]), level, iter);
587                if d > 0 {
588                    self.cap[e] -= d;
589                    self.cap[e ^ 1] += d;
590                    return d;
591                }
592            }
593            iter[u] = self.next[iter[u] as usize];
594        }
595        0
596    }
597
598    fn max_flow(&mut self, source: usize, sink: usize) -> i64 {
599        let mut flow = 0i64;
600        let mut level = vec![0i32; self.n];
601        let mut iter = vec![0i32; self.n];
602        while self.bfs(source, sink, &mut level) {
603            iter.copy_from_slice(&self.head);
604            loop {
605                let f = self.dfs(source, sink, i64::MAX, &level, &mut iter);
606                if f == 0 {
607                    break;
608                }
609                flow += f;
610            }
611        }
612        flow
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619    use std::process::Command;
620
621    #[test]
622    fn test_schrage_rand_seed_270001() {
623        let mut seed: i32 = 270_001;
624        let r1 = schrage_rand(&mut seed);
625        assert_eq!(seed, 242_939_513);
626        assert!((r1 - 0.11313).abs() < 1e-4, "r1 = {r1}");
627
628        let r2 = schrage_rand(&mut seed);
629        assert_eq!(seed, 717_982_044);
630        assert!((r2 - 0.33434).abs() < 1e-4, "r2 = {r2}");
631    }
632
633    #[test]
634    fn test_unif() {
635        let mut seed: i32 = 270_001;
636        assert_eq!(unif(10_000, &mut seed), 1132);
637        assert_eq!(unif(1_000, &mut seed), 335);
638    }
639
640    #[test]
641    fn test_arc_count() {
642        assert_eq!(2 * (3 - 1) * 3 + 3 + 3, 18);
643        assert_eq!(2 * (8 - 1) * 8 + 8 + 8, 128);
644    }
645
646    #[test]
647    fn test_node_numbering() {
648        assert_eq!(node(1, 1, 3), 1);
649        assert_eq!(node(2, 3, 3), 6);
650        assert_eq!(node(3, 3, 3), 9);
651    }
652
653    #[test]
654    fn e2e_3x3_known_output() {
655        let params = GridGraphParams::new(3, 3, 100, 10, 12345).unwrap();
656        let expected = "\
657c    max-flow min-cost on a  3 by   3 grid
658c    cap: [0,     100]   cost: [0,      10]   seed:     12345
659p min              11             18
660n              10             40
661n              11            -40
662a          1         2 0         10         8
663a          1         4 0         94         1
664a          4         5 0          6         7
665a          4         7 0         58         9
666a          7         8 0         33         2
667a          2         3 0         79         9
668a          2         5 0         15         4
669a          5         6 0         27         2
670a          5         8 0         86         2
671a          8         9 0         24         9
672a          3         6 0         31         6
673a          6         9 0         51         2
674a         10         1 0        104         1
675a         10         4 0         64         8
676a         10         7 0         33         3
677a          3        11 0         79         1
678a          6        11 0         58         9
679a          9        11 0         75         9
680";
681        let actual = generate_dimacs(params);
682        assert_eq!(actual, expected);
683    }
684
685    fn run_fortran(input: &str) -> Option<String> {
686        let manifest = env!("CARGO_MANIFEST_DIR");
687        let fortran_dir = format!("{manifest}/gridgraph_original");
688
689        let status = Command::new("make")
690            .args(["-C", &fortran_dir])
691            .status()
692            .ok()?;
693        if !status.success() {
694            return None;
695        }
696
697        let binary = format!("{fortran_dir}/ggraph");
698        let output = Command::new(&binary)
699            .stdin(std::process::Stdio::piped())
700            .stdout(std::process::Stdio::piped())
701            .stderr(std::process::Stdio::piped())
702            .spawn()
703            .and_then(|mut child| {
704                use std::io::Write;
705                child
706                    .stdin
707                    .as_mut()
708                    .unwrap()
709                    .write_all(input.as_bytes())
710                    .unwrap();
711                child.wait_with_output()
712            })
713            .ok()?;
714
715        if !output.status.success() {
716            return None;
717        }
718        Some(String::from_utf8(output.stdout).unwrap())
719    }
720
721    #[test]
722    fn e2e_3x3_vs_fortran() {
723        let params = GridGraphParams::new(3, 3, 100, 10, 12345).unwrap();
724        let input = "3 3 100 10 12345";
725        if let Some(fortran_out) = run_fortran(input) {
726            let rust_out = generate_dimacs(params);
727            assert_eq!(rust_out, fortran_out);
728        } else {
729            eprintln!("Skipping Fortran comparison: could not build/run Fortran binary");
730        }
731    }
732
733    #[test]
734    fn e2e_8x8_vs_fortran() {
735        let params = GridGraphParams::new(8, 8, 10_000, 1_000, 270_001).unwrap();
736        let input = "8 8 10000 1000 270001";
737        if let Some(fortran_out) = run_fortran(input) {
738            let rust_out = generate_dimacs(params);
739            assert_eq!(rust_out, fortran_out);
740        } else {
741            eprintln!("Skipping Fortran comparison: could not build/run Fortran binary");
742        }
743    }
744
745    #[test]
746    fn e2e_8x8_spot_check() {
747        let params = GridGraphParams::new(8, 8, 10_000, 1_000, 270_001).unwrap();
748        let output = generate_dimacs(params);
749        let lines: Vec<&str> = output.lines().collect();
750        assert!(lines[2].contains("66"));
751        assert!(lines[2].contains("128"));
752        assert!(lines[3].contains("12458"));
753        assert_eq!(lines[5], "a          1         2 0       1132       335");
754    }
755}