Skip to content

Instantly share code, notes, and snippets.

@maelvls
Last active April 27, 2019 01:33
Show Gist options
  • Save maelvls/755c16db4681e3a671c1 to your computer and use it in GitHub Desktop.
Save maelvls/755c16db4681e3a671c1 to your computer and use it in GitHub Desktop.
DIMACS Maximum Flow to .dot (GraphViz) converts from DIMACS to DOT format. I did this script to visualize the results from the `pseudo-flow` algorithm written by Hochbaum (http://lpsolve.sourceforge.net/5.5/DIMACS_maxf.htm). Short link: https://git.io/vdhsz
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright 2015 Mael Valais <mael.valais@univ-tlse3.fr>
#
# Distributed under terms of the MIT license.
#
__descr__ = """
This script is a parser from DIMACS to DOT that helps you transform
the max-flow DIMACS files into DOT files.
"""
#
# Terminology:
# - 'problem.max' = the problem written in DIMACS
# - 'results' = the results of ./pseudo, the max-flow solver
#
# Example 1 (display problem.max only):
#
# ./dimacs_to_dot.py problem.max | dot -Tpng > problem.png
#
# Example 2 (using the result of ./pseudo):
#
# cat problem.max | pseudo \\
# | ./dimacs_to_dot.py problem.max -rstdin | dot -Tpng > problem.png
#
# NOTES:
# 1) the `cn 2 NAME` are comment lines, but are used in this script to give
# names to the vertices.
# 2) `./pseudo` is the name of the max-flow solver. You can get the source
# and compile it here: http://lpsolve.sourceforge.net/5.5/DIMACS_maxf.htm
#
# Content of problem.max:
#
# p max 10 15
# n 1 s
# n 10 t
# cn 2 A
# cn 3 B
# cn 4 C
# cn 5 D
# cn 6 E
# cn 7 Mac
# cn 8 Linux
# cn 9 Windows
# a 1 7 2
# a 1 8 2
# a 1 9 3
# a 7 2 1
# a 7 4 1
# a 7 6 1
# a 7 3 1
# a 8 3 1
# a 8 5 1
# a 9 5 1
# a 2 10 1
# a 3 10 1
# a 4 10 1
# a 5 10 1
# a 6 10 1
import re
import sys
import argparse
# Parses the DIMACS file and produces `edges` and `vertices`
# In: the DIMACS input file already open
# Out: [vertices, edges] with
# edges = [[vertex1, vertex2],...]
# vertices = [vertex1, vertex2...]
def parse_dimacs(dimacs_file):
edges = []
vertices = []
for line in dimacs_file:
vals = [i for i in re.findall("(\d+|\w+)",line)]
if vals:
if vals[0] in ["cn","n"]:
vertices += [vals[1:]]
if vals[0] == "a":
edges += [vals[1:]]
return [vertices, edges]
# Parses a result file that has the form
# Form: `starting_char vertex1 vertex2 flow_value`
# Ex: `a 4 1 90`
# Call: parse_result("file.result",'a',[[1,2],...]
# In: starting_char is the character that introduces each
# new result line
# In/out: edges with the form [[1,2],...]
# In: result_file_name is the input file already open
# and add a column into edge
def parse_result(result_file, starting_char, edges):
for line in result_file:
vals = [i for i in re.findall("(\d+|\w+)",line)]
if vals and vals[0] == starting_char:
vals = vals[1:]
i = next(i for i in range(len(edges)) if \
[vals[0],vals[1]]==[edges[i][0],edges[i][1]])
edges[i] += [vals[2]]
return edges
# Creates the dot file and write it into the output_file
def print_dot_file(edges, vertices, output_file):
print('digraph a \n')
print('{ \n')
print('\tgraph [rankdir=LR];')
for v in vertices:
print('\t%s [label=%s];' % (v[0], v[1]))
for e in edges:
if len(e) == 3:
print('\t%s -> %s [label="(%s)" fontsize=11];' % (e[0], e[1], e[2]))
if len(e) == 4:
print('\t%s -> %s [label=<(%s)> fontsize=11 xlabel=<<font color=\'red\'>%s</font>>];' % (e[0],e[1],e[2],e[3]))
print('}')
parser = argparse.ArgumentParser(description=__descr__,add_help=True)
parser.add_argument("dimacs_file", nargs='?',\
type=argparse.FileType('r'), default=sys.stdin,\
help='the input DIMACS file')
parser.add_argument("-o", nargs='?',\
type=argparse.FileType('w'), default=sys.stdout, \
help='the output file in DOT format', metavar='output')
parser.add_argument("-r", required=False, nargs=1, \
type=argparse.FileType('r'),\
help='the optional result file given by your solver (see -c)',\
metavar='result_file')
parser.add_argument("-rstdin", required=False, action="store_true", default=False,\
help='use stdin as the input for the result file')
parser.add_argument("-c",nargs=1,\
help='use a different starting char for the result file',\
metavar='starting_char', default='a')
args = parser.parse_args()
# 0. Checking args
# Exclusive or: either stdin OR a file name
result_has_been_given = False
if (args.rstdin == True or args.r != None):
result_has_been_given = True
if not (args.rstdin == True) ^ (args.r != None):
print("error: can't have both -rstdin and -r",file=sys.stderr)
sys.exit(1)
elif args.rstdin == True:
# We must check that sys.stdin is not already used
# by dimacs_file
if args.dimacs_file != sys.stdin:
res_file = sys.stdin
else:
print("error: you must give an explicit dimacs file name \
when using -rstdin",file=sys.stderr)
sys.exit(1)
else:
res_file = open(args.r,'r')
# 1. Parse the dimacs file
[vertices,edges] = parse_dimacs(args.dimacs_file)
# 2. Parse the result file if given
if result_has_been_given:
edges = parse_result(res_file, args.c, edges)
# 3. Produce the dot file
print_dot_file(edges,vertices,args.o)
@maelvls
Copy link
Author

maelvls commented Apr 9, 2015

The script will recognize special comments beginning with cn ; the syntax is

cn <node_number> <label for this node>

For example, if I want the node 9 to be displayed using the label G, I will use cn 9 G.

Problem file example.max

c Le chateau d'eau
p max 7 9
n 1 s
n 7 t
cn 2 A
cn 3 B
cn 4 C
cn 5 D
cn 6 E
a     1      2     50
a     1      3     80
a     2      4     40
a     2      5     20
a     3      5     50
a     3      6     20
a     4      7     70
a     5      7     30
a     6      7     30

You can get the picture of the flow (example.png) using the command:

python3 dimacs_to_dot.py example.max | dot -Tpng > example.png

@maelvls
Copy link
Author

maelvls commented Apr 9, 2015

With the file example.max:

p max 10 15
n  1   s
n  10  t
cn 2   A
cn 3   B
cn 4   C
cn 5   D
cn 6   E
cn 7   Mac
cn 8   Linux
cn 9   Windows
a  1   7   2
a  1   8   2
a  1   9   3
a  7   2   1
a  7   4   1
a  7   6   1
a  7   3   1
a  8   3   1
a  8   5   1
a  9   5   1
a  2   10  1
a  3   10  1
a  4   10  1
a  5   10  1
a  6   10  1

and the command

python dimacs_to_dot.py example.max | dot -Tpng > example.png

you get:

@maelvls
Copy link
Author

maelvls commented Apr 9, 2015

With the file tondeuses-a-gazon.max (this file represents the flow problem):

p max 12 15
n 1 s
n 12 t
cn 2 X
cn 3 Y
cn 4 Z
cn 5 A
cn 6 B
cn 7 C
cn 8 u
cn 9 v
cn 10 w
cn 11 r

a 1 2 500
a 1 3 500
a 1 4 900
a 2 10 300
a 3 10 200
a 3 9 600
a 4 8 700
a 8 11 500
a 8 3 300
a 10 5 600
a 9 6 500
a 9 7 200
a 11 7 200
a 5 12 700
a 6 12 600
a 7 12 600

File res generated by pseudo tondeuses-a-gazon.max :

c Highest label pseudoflow algorithm (Version 3.23)
c Using FIFO buckets
c Number of nodes     : 12
c Number of arcs      : 15
c Time to read        : 0.000
c Time to initialize  : 0.000
c Time to min cut     : 0.000
c Time to max flow    : 0.000
c Number of arc scans : 18
c Number of mergers   : 11
c Number of pushes    : 16
c Number of relabels  : 19
c Number of gaps      : 4
c
c Solution checks as feasible.
c
c Solution checks as optimal.
c
s Max Flow            : 900
c
c Nodes in source set of min s-t cut:
n 1
n 2
n 3
n 4
n 6
n 8
n 9
n 11
c
c Flow values on each arc:
a 1 2 300
a 1 4 500
a 3 10 200
a 8 11 200
a 8 3 300
a 10 5 500
a 9 6 0
a 5 12 500
a 7 12 400
a 11 7 200
a 9 7 200
a 4 8 500
a 3 9 200
a 2 10 300
a 1 3 100

Using the following command, you will get the picture example.png:

python3 dimacs_to_dot.py tondeuses-a-gazon.max res | dot -Tpng > example.png

@maelvls
Copy link
Author

maelvls commented Oct 24, 2017

For the problem of factories and warehouses, here is the file factories.max:

c Usines U1 et U2
p max 7 11
n 6 s
n 7 t
cn 1 U1
cn 2 U2
cn 3 E1
cn 4 E2
cn 5 E3
a 6 1 100
a 6 2 150
a 3 7 50
a 4 7 70
a 5 7 80
a 1 3 1000
a 1 4 1000
a 1 5 1000
a 2 3 1000
a 2 4 1000
a 2 5 1000

And the command to launch:

cat factories.max | pseudo | python3 dimacs_to_dot.py -rstdin factories.max | dot -Tpng > factories.png

The result (factories.png):
ex1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment