Archive for October, 2010

Python extensions with C libraries made easy by Cython

16 October, 2010

Suppose you have a library written in C that you like to be called from Python. There are many ways to accomplish this, and I would like to show here a complete example of doing this using Cython, which is basically a compiler from (an extension of Python) to C. Most of this, and much more, can be found in a quick tutorial.
For the purpose of an example, suppose you have a C function that computes the average of an array of doubles (all source files here, as well as the Makefile that basically contains the shell commands we show below, can be downloaded here, or cloned using git from github):

/* cmean.c */
double mean(int n, double* a)
{
double s;
int i;
for (s=0., i=0; i<n; i++) s+=*(a++);
return s/n;
}

with the prototype

/* cmean.h */
double mean(int, double*);

and you want to call it from Python, i.e. (we need to give the function another name here, cmean(), for technical as well as for semantic reasons—noone would pass the length of an array and the array to a function in Python, as the array itself would certainly do) say

b=[1.3,5.777777,-12.0,77.]
print cmean(b)

would print the same as

print sum(b)/len(b)

As cmean() is not a Python built-in, we would expect to import it first. So the following

$ python test.py

where

# test.py
import dyn
from dyn import cmean
b=[1.3,5.777777,-12.0,77.]
print cmean(b)
print sum(b)/len(b)

will print the number 18.01944425 twice. OK, so here is cmean() definition in Cython (note the file extension .pyx)

# m.pyx
cdef extern from "cmean.h":
double mean(int, double*)
from stdlib cimport *
def cmean(a):
n = len(a)
cdef double *v
v = malloc(n*sizeof(double))
for i in range(n):
v[i] = float(a[i])
m = mean(n, v)
free(v)
return m

The main task of cmean() is to create a “plain” C array of doubles, pass it to our C function, get the result, clean after itself, and return the result. Unlike Python, Cython has types declarations. Unless one calls C-function from the Cython code, they are not mandatory though, although they can speed the things up tremendously in “real” computations. In the example here

cdef double *v

cannot be avoided — the code does not compile without it. In a usual C-like fasion, instead of 2 lines

cdef double *v
v = malloc(n*sizeof(double))

one could have written just

cdef double *v = malloc(n*sizeof(double))

The top two lines pass the prototype declaration of the C-function mean to be included into the C file to be generated, and the third line imports the functions from the C library stdlib (we need malloc and free from there).

Having this, we need to create the extension module dyn, to be imported by Python. This is perhaps the least intuitive part of the job. One way to accomplish it is to use Python distutils: i.e. we need to create a setup.py file to be called as

$ python setup.py build_ext --inplace

This should look as follows:

# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("dyn",
["m.pyx"],
library_dirs = ['.'],
libraries=["cmean"]) # Unix-like specific
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)

One not yet explained thing here is how the shared library containing the compiled C-function mean() is hooked up to dyn. You see in the code above the declarations library_dirs and libraries. They assume that the shared library is called libcmean.so and that it is in the same directory (i.e. ‘.’ is the path to it) as the rest of the code. One can create libcmean.so (before creating dyn, more precisely, a file called dyn.so) by running

$ gcc -fPIC -shared cmean.c -o libcmean.so

One more point to watch is that on some Unix systems the loader will be unable to locate the dynamic libraries libcmean.so and/or dyn.so we created (unless they are placed in certain stanadard directories, which is not something one wants to do with experimental code!). So test.py will need to be run, e.g., as follows:

$ LD_LIBRARY_PATH=. python test.py

For the sake of completeness, the aforementioned archive contains a C file that calls mean from libcmean.so, and the corresponding entries in the Makefile can do the necessary work to build and run it.

Last but not least, Sage automates parts of the interface building even better.

Advertisements

Integer flows and graph theory applications

14 October, 2010

Here we are continuing the story of Ford-Fulkerson algorithm. Assume that {c:D\rightarrow {\mathbb Z}_+}, i.e. the capacities are all integers. Then starting the Ford-Fulkerson from the zero flow, we see that at each iteration {\tau(P)\in {\mathbb Z}_+}. Thus the optimal flow will have integer values for each arc (such flows are called integral). We have

Corollary 3 A maxflow problem with integer capacities has an integral optimal solution.

This has various graph-theoretic applications. We can convert an undirected graph {\Gamma=(V,E)} into a digraph by replacing each edge {\{x,y\}} by a pair of opposite arcs {xy} and {yx}, set all arc capacities to 1, and see what this implies for a pair of non-adjacent vertices {s\neq t\in V}. Running Ford-Fulkerson algorithm, we obtain an integral maximum {s-t} flow {f}, of value {k:={\mathrm val}(f)}.

We also know that there is, by the maxflow-mincut Theorem, an {s-t} cut {(S,V\setminus S)} s.t. {{\mathrm cap}(S,V\setminus S)={\mathrm val}(f)}. Instead of capacity, it is more appropriate to talk here about the size {|(S,V\setminus S)|} of the cut, i.e. the number of edges crossing from one part of the cut to another. Obviously, by definition of the capacity, we have {{\mathrm cap}(S,V\setminus S)=|(S,V\setminus S)|}.

The flow {f} can be viewed as a collection {D_f} of {k} edge-disjoint {\Gamma}-paths from {s} to {t}, as can be seen by induction on {{\mathrm val}(f)}: indeed, if {{\mathrm val}(f)=1} then by construction (via the Ford-Fulkerson algorithm) {D_f} is a path. Suppose now this statement holds for all {k<{\mathrm val}(f)}. As {D_f} induces a connected subgraph, it contains a shortest path {P}. Then, {P} is cut by a minimum cut {(S,V\setminus S)}—otherwise the cut does not cut! Removing all the edges of {P} from {D}, we thus obtain a graph {\Gamma'} that has the cut {(S,V\setminus S)} of size {{\mathrm val}(f)-\ell}, for {\ell>0}. The flow {f'} in {\Gamma} corresponding to {D_f\setminus P} has value {{\mathrm val}(f)-\ell}, and {P} is an augmenting path for this flow, with {\tau(P)=1}. Thus {\ell=1}, i.e. {(S,V\setminus S)} cuts just 1 edge in {P}, and by induction {\Gamma'} contains {k-1} edge-disjoint {\Gamma}-paths, and we obtain

Corollary 4 (Menger Theorem (for edge-disjoint paths)) The minimum size of an {s-t} cut in {\Gamma} equals the maximal number of edge-disjoint {s-t} paths in {\Gamma}.

There is a graph transformation that allows one to prove Menger Theorem for internally disjoint {s-t} paths (two paths between {s} and {t} are called internally disjoint is their only common vertices are {s} and {y}). Our goal is to prove the following.

Theorem 5 (Menger Theorem (for internally disjoint paths)) The minimum size of an {s-t} cut in {\Gamma} equals the maximal number of internally disjoint {s-t} paths in {\Gamma}.

Proof: Assume {s} and {t} are not adjacent. Replace each {v\in V\setminus\{s,t\}} by a pair of vertices {v_+}, {v_-}, joined by an arc {v_+ v_-}. Then replace each edge {\{x,y\}} of {\Gamma} by a pair of opposite arcs {xy} and {yx}. Replace every arc {xy} such that {s,t\not\in \{x,y\}} by the arc {x_{-}y_{+}}, each arc {xy} with {x=s} or {x=t} by {xy_+}, and each arc {xy} with {y=s} or {y=t} by {x_-y}. Now each {s-t} path {s,x^1,\dots,x^{k-1},t} corresponds to the path {s,x^1_+,x^1_-,x^2_+,\dots,x^{k-1}_+,x^{k-1}_-,t} in the new graph, and the opposite holds, too: each {s-t} path in the new graph has this form.

Assign capacities 1 to the arcs {v_+ v_-}, and infinite capacities (it suffices to take this “infinity” to be bigger than, say, {10|E\Gamma|}) to the rest of the arcs. Due to the choice of capacities, a minimum {s-t} cut {(S,V\setminus S)} will satisfy {{\mathrm cap}(S,V\setminus S)\leq |E\Gamma|}, and thus the only arcs from {S} to {V\setminus S} will be of type {v_+v_-}. Say, there will be {k} such arcs. By (a straightforward generalisation of) Menger Theorem for edge-disjoint paths (to directed graphs), there will be exactly {k} arc-disjoint {s-t} paths in this digraph. By its construction, they cannot share a vertex {v_-} (resp. {v_+}), as there is only one outgoing (resp. incoming) arc on it. Thus these paths are internally disjoint, and the corresponding {k} paths in {\Gamma} are internally disjoint, too. \Box

One more classical application is a maxflow-based algorithm to construct a maximum matching in a bipartite graph {\Gamma=(V,E)}, with the bipartition {V=V_s\cup V_t}. We add two more vertices {s} and {t} to it, and connect {x\in\{s,t\}} to each {v\in V_x} by an edge. In the resulting new graph {\Gamma'} we solve the {s-t} maxflow problem with all capacities set to 1. More precisely, we orient each edge so that each {\{v_s,v_t\}}, {\{s,v_s\}}, {\{v_t,t\}} becomes an arc {v_sv_t}, respectively, {sv_s}, respectively {v_tt}.

Ford-Fulkerson algorithm, when started from the zero flow, will produce an integral maximal flow, of value {k:={\mathrm val}(f)}. As we know from Menger’s theorem, we will have {k} internally disjoint {s-t}-paths. They will induce a matching {M_f} on {\Gamma} (just look at the {\{v_s,v_t\}} edges only). On the other hand, any matching {M} on {\Gamma} can be used to construct an {s-t}-flow of value {|M|} in {\Gamma'} with all capacities 1. Hence {M_f} is a maximum matching in {\Gamma}.

Augmenting paths for Maxflow and Mincut

11 October, 2010

We discussed how to formulate the MAXFLOW problem for networks, i.e. arc-weighted digraphs {(V,D,c)} with two specific vertices, source {s} and sink {t} and arc capacities {c:D\rightarrow{\mathbb R}_+}, and treat it via linear programming. An alternative approach is via augmenting paths. The paths we talk about will disregard the arc directions in {D}, i.e. they will be {s}{t} paths in the underlying undirected graph. (When {e\in P} has the direction opposite to the arc in {D}, we write {e\in P\setminus D}.)

Definition 1 Given an {s}{t} flow {f:D\rightarrow {\mathbb R}_+}, an {f}augmenting path is an {s}{t} path {P} in the underlying graph, such that for each {e\in P}

  • if {e\in D} then {f(e)<c(e)};
  • if {e\not\in D} then {f(e)>0}.

The tolerance of {P} is

\displaystyle \tau(P):=\min(\min_{e\in P\setminus D} f(e), \min_{e\in P\cap D}c(e)-f(e)).

Note that the definition of tolerance extends unchanged to any path starting at {s}.

We can use {P} to improve {f}, as follows.

Lemma 2 For each {e\in D}, let

\displaystyle f'(e)=\begin{cases} f(e) & e\not\in P\\ f(e)-\tau(P) & e\in P\setminus D\\ f(e)+\tau(P) & e\in P\cap D \end{cases}.

Then {f'} is an {s}{t} flow in {(V,D,c)}, of value greater than that of {f} by {\tau(P).}

Proof: It is immediate from the definition of {\tau(P)} that {0\leq f'(e)\leq c(e)} for any {e\in D}. We still need to check the flow conservation constraints

\displaystyle \sum_{x: xv\in D} f'((xv))=\sum_{x: vx\in D} f'((vx))\quad\forall v\in V\setminus\{s,t\}.

If {P} visits {v\in V\setminus\{s,t\}} then there are two arcs, say, {xv} and {vy} on {v} that are affected when we change {f} to {f'}, and the changes in flow values cancel each other in each of the four cases (recall that {P} is a path in the underlying undirected graph). Finally, the value of {f'} is the net outflow from {s}, and the latter is greater than that of {f} by {\tau(P)}. \Box

This suggests finding a maximum flow by repeatedly finding an augmenting path and improving the current flow with it, till no augmenting path is available (this is, for instance, how Ford-Fulkerson algorithm is working). For this to succeed, we obviously need to prove that {f} is maximum if and only if it does not have an {f}-augmenting path.

Augmenting paths are found by a breadth-first search from {s}. At some point, if we did not succeed in reaching {t}, we would end up with the set of vertices {S} reachable, in the underlying non-oriented graph, by paths with positive tolerance. Each {e\in D} crossing over from {S} to {T=V\setminus S} satisfies the property that {c(e)=f(e)}, and each arc {e} crossing from {T} to {S} satisfies {f(e)=0} — otherwise we would have added their vertices in {T} to {S}. Therefore,

\displaystyle {\mathrm val} (f)=\sum_{xy\in D: x\in S, y\in T} c((xy))=:{\mathrm cap} (S,T),

where on the right we have the definition of capacity of an arbitrary {s}{t} cut {(S,T)}.

It is easy to show that for an arbitrary {s}{t} cut {(S',T')}, one has {{\mathrm cap} (S',T')\geq {\mathrm val} (f)}. Thus we have have obtained the Maxflow-Mincut Theorem, and also proved that our algorithm, to be described in more detail shortly, finds a maximum flow {f}, and a corresponding to it minimum cut {(S,T)}.

Breadth-first search: Ford-Fulkerson algorithm

As usual in breadth-first search, we use a queue, i.e. a data structure {(Q,p,q)} that has two indices, one, {q}, pointing to the end of the queue, for new arrivals, and the other, {p}, pointing to the beginning of the queue, i.e. to the first element to be processed. There will be elementary operations:

  • {{\mathrm Insert} (Q,x)} – adds a new element, {x}, to the queue, and icrements {q}. In this case we also do not allow an element to enter the queue twice, so {{\mathrm Insert} (Q,x)} has no effect if {x} already was these once.
  • {x:={\mathrm First} (Q)} – takes the (currently) first element, {x}, from the queue, and icrements {p}—this will fail if {Q} is empty, i.e. if {p=q}.

Now the algorithm is as follows:

  1. Input: a feasible flow {f} in {(V,D,c)} with source {s} and sink {t}.
  2. Initialization: let {(Q,p,q)} be an empty queue; {{\mathrm Insert} (Q,s)}, and set {S:=\{(s,\infty)\}}.
  3. Loop: take {x:={\mathrm First} (Q)}. If this fails, return the first coordinates of the pairs in {S}—a minimum cut.
  4. for {v\in V} s.t. {(xv)\in D} and {f((xv))<c((xv))}, or s.t. {(vx)\in D} and {f((vx))>0}:
    • {{\mathrm Insert} (Q,v)}, {S:=S\cup\{(v,x)\}}.
    • if {v=t} then return the augmenting {s}{t} path by tracing back the pairs {(t,v_t)}, {(v_t,v_{t-1})},\dots, {(v_1,s)} in {S}.
  5. go to Loop.

So if {f} is not maximum we get an {f}-augmenting path, that can be used to improve {f}, and repeat. However, the speed of this procedure becomes unacceptable when the capacities are large. If {c\in\mathbb{Q}^{|D|}} then we are at least assured that it will converge after finitely many steps: indeed, we can multiply {c} by the least common multiple of its denominators, reducing to the case {c\in\mathbb{Z}^{|D|}}. Each iteration in this case improves the flow by at least {1}. However, the time would not be polynomial in the input of the problem. E.g. for the following graph the number of iterations can be as large as {2M}, when {M} is a large integer.

For irrational {c} the convergence not even need to be the case!

If one chooses a shortest {f}-augmenting path to improve {f} with, then the running time becomes bounded by {O(|V||D|^2)}—that was shown by E.Dinitz in 1970 and independently by J.Edmonds and R.Karp in 1972. Details, and further improvements by A.Karzanov et.al can be found e.g. in A.Schrijver’s lecture notes, Sect.4.4.

Edmonds algorithm for maximum matchings

5 October, 2010

We continue with matchings in simple graphs, following the exposition in A.Schrijver’s 3-volume work, and his lecture notes available online, filling in few more details.

While in a bipartite graph {\Gamma=(V,E)} and a matching {M\subset E} it is always the case that a shortest path from the set {X\subset V} of {M}-unsaturated vertices to itself (i.e. an {X}{X}-path) is always {M}-augmenting, this is no longer true in general graphs:

here {X=\{a,b\}}, {M} is indicated by thicker edges, and the shortest path (of length 4) between them is not augmenting. This precludes the algorithmic idea of augmenting a matching {M} using an augmenting path (found by a shortest path procedure in a digraph constructed from {\Gamma} and {M}) from working in the general case. Edmonds came up with an idea that one can allow for repeated vertices, and shink the arising in the process cycles to (new) vertices.

Definition 1 An {M}-alternating walk {P=(v_0,\dots,v_t)} is a sequence of vertices (some of which can repeat!) that is a walk (i.e. {v_i v_{i+1}\in E} for all {0\leq i\leq t-1}) so that exactly one of {v_{i-1} v_i} and {v_i v_{i+1}} is in {M}.

Definition 2 An {M}-alternating walk {P=(v_0,\dots,v_t)} is an {M}flower if {P} has an even number of vertices (as a sequence, i.e. {t} is odd), {v_0\in X}, all the {v_0,\dots,v_{t-1}} are distinct, and {v_t=v_{2k}} for {2k<t.} (Removing the vertex {b} and the edge {bs} on the picture above is an example of an {M}-flower).
The cycle {(v_{2k},v_{2k+1},\dots, v_t)} in an {M}-flower is an {M}blossom. ({(qvuts)} on the picture above is an example of an {M}-blossom.)

Once we have an {M}-blossom {B}, we can shrink it by replacing all of its vertices {V(B)} with just one vertex (call it {B}, too), replacing each edge {uv}, {v\in V(B)} with the edge {uB} (and ignoring any loops that arise on {B}). The resulting graph is denoted by {\Gamma/B=(V/B, E/B)}. (E.g. shrinking the {M}-blossom {B} on the picture above produces the graph that is the path {apBb}). In {\Gamma/B} one will have the matching {M/B}, where {B} will become just a saturated vertex.

Theorem 3 {M} is a maximum matching in {\Gamma} if and only if {M/B} is a maximum matching in {\Gamma/B}, for an {M}-blossom {B}.

Proof: If {M/B} is not a maximum matching in {\Gamma/B} then there will be an {M/B}-augmenting path {P} in {\Gamma/B.} If {P} does not meet {B=(v_{2k},\dots,v_t)} then it corresponds to an {M}-augmenting path {P'} in {\Gamma}. So we can assume that {uB\not\in M} is an edge of {P}, and {Bu'\in M} is also an edge of {P}. As {uB\in E/B}, there exists {2k\leq j<t} so that {uv_j\in E.} Now, depending upon the parity of {j}, we replace {B} in {P} by the part of the blossom rim {v_j,v_{j\pm 1},v_{j\pm 2},\dots,v_t}, where the sign in the indices is {+} for {j} odd, and {-} for {j} even.

Conversely, if {M'} is not a maximum matching in {\Gamma}, then

\displaystyle M=M'\Delta \{v_0v_1,\dots,v_{2k-1}v_{2k}\}

is also a matching of the same size, and {|M/B|=|M'/B|}. Thus it suffices to show that {M/B} is not maximal in {\Gamma/B}, and we will assume w.l.o.g. that {k=0}, i.e. {v_{2k}=v_0\in X} (our flower has no stem). There exists an {M}-augmenting path {P=(u_0,\dots,u_s)}. W.l.o.g. {P} does not start in {B} (we can reverse it if it does). If {P} does not contain a vertex in {B}, it will continue to be an {M/B} augmenting path in {\Gamma/B}. Otherwise let the first vertex of {P} in {B} be {u_j}. Then {(u_0,\dots,u_{j-1},B)} is an augmenting path in {\Gamma/B}. \Box

For the algorithm we will need

Theorem 4 A shortest {X} to {X} {M}-alternating walk is either a path or its initial segment is an {M}-flower.

Proof: Assume {P=(v_0,\dots,v_t)} is not a path, and let {i<j} be such that {v_i=v_j} and {j} as small as possible. We will show that {(v_0,\dots,v_j)} is an {M}-flower, and we immediately have it if {i} is even and {j} is odd.

If {j-i} were even, as shown above, where the walk might have been {(apqstpqb)}, and so {j=5}, {i=1}, then the path {(v_i,\dots,v_{j-1})} is {M}-alternating, so we would simply delete it from {P} and obtain a shorter {M}-alternating walk.

If {i} were odd and {j} even, we would have {v_iv_{i+1}} and {v_{j-1}v_j} in {M}, so {v_{i+1}=v_{j-1}}, contradicting the choice of {j}. \Box

Combining these two observations, we obtain the following augmenting path algorithm:

  1. Input: a graph {\Gamma} and a matching {M}.
  2. Let {X} be the set of vertices unsaturated by {M}. Construct, if possible, a positive length shortest walk {P} from {X} to {X}.
  3. If no {P} was constructed, return {\emptyset}{M} is maximum.
  4. If {P} is a path then return it.
  5. Otherwise take the first blossom {B} from {P}, call the algorithm recursively on {\Gamma/B} and {M/B}, and return the expanded to an augmenting path in {\Gamma} resulting path in {\Gamma/B}.

Applying the algorithm starting from {M=\emptyset}, and augmenting {M} with the path it returns, we obtain an efficient algorithm, running in fact in {O(|V|^2|E|)} operations.

We still need to discuss here how to do Step 2. To this end we construct a directed graph {D_M=(V,A\cup A')} with

\displaystyle A:=\{(u,v)\mid u\neq v\in V, \exists ux\in E, xv\in M\}

\displaystyle A':=\{(u,v)\mid u\neq v\in X, \exists uv\in E\}

Lemma 5 Each {X} to {X} {M}-alternating path, resp. each {M}-flower, corresponds to a directed path in {D} from {X} to {\Gamma(X)}—the set of neighbours of {X} in {\Gamma.} Conversely, each {X} to {\Gamma(X)} directed path in {A} corresponds either to an {M}-augmenting path or to an {M}-flower.

Proof: (Sketch) Each length 1 {M}-augmenting path {uv} obviously corresponds to the pair of arcs {uv} and {vu} in {A'}.

If {u_0,u_1,\dots,u_s} is a {A}-path in {D}, so that {u_0\in X}, {u_t\in\Gamma(X)}, then there is a unique, up to the choice of {u_{s+1}\in X}, lifting of it to an {M}-alternating walk {P=u_0,v_1,u_1,v_2,u_2,\dots,v_s,u_s,u_{s+1}}, with {v_iu_i\in M} for {1\leq i\leq s}. If {P} is a path then it is {M}-augmenting.

If {P} has repeated vertices, this repetition occurs in a regular way, corresponding to an {M}-flower. As we move along {P} from {u_0} to {u_s}, let {u_t=v_i}, {t>i}, be the first repetition. Then {u_i} is the beginning of the blossom, and {u_{t-1}} the last vertex of the blossom. \Box