From 0c9584e5bfe75691f67fefaa15d878cec5764bc0 Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 10:39:46 -0500 Subject: [PATCH 1/8] Translate push-relabel from Kactl --- pyrival/graphs/push_relabel.py | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 pyrival/graphs/push_relabel.py diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py new file mode 100644 index 0000000..2b26e13 --- /dev/null +++ b/pyrival/graphs/push_relabel.py @@ -0,0 +1,89 @@ +""" +Original C++ +Author: Simon Lindholm +Date: 2015-02-24 +License: CC0 +Source: Wikipedia, tinyKACTL +Description: Push-relabel using the highest label selection rule and the gap heuristic. Quite fast in practice. + To obtain the actual flow, look at positive values only. +Time: $O(V^2\sqrt E)$ +Status: Tested on Kattis and SPOJ, and stress-tested + +Translated by: Whosyourjay +Date: 2022-11-25 +""" + +class Edge: + def __init__(self, dest, back, f, c): + self.dest = dest + self.back = back + self.f = f + self.c = c + +class PushRelabel: + def PushRelabel(self, n): + self.g = [[] for _ in range(n)] + self.ec = [0]*n + self.cur = [0]*n + self.hs = [0]*(2*n) + self.H = [[] for _ in range(n)] + + def add_edge(self, s, t, cap, rcap=0): + if s == t: + return + self.g[s].append(Edge(t, len(self.g[t]), cap, 0)) + self.g[t].append(Edge(s, len(self.g[s]) - 1, rcap, 0)) + + def add_flow(self, e, f): + back = self.g[e.dest][e.back] + if (not self.ec[e.dest]) and f: + self.hs[self.H[e.dest]].append(e.dest) + e.f += f + e.c -= f + self.ec[e.dest] += f + + back.f -= f + back.c += f + self.ec[back.dest] -= f + + def calc(s, t): + v = len(self.g) + self.H[s] = v + self.ec[t] = 1 + co = [0] * (2*v) + co[0] = v-1 + for i in range(v): + self.cur[i] = 0 #.data() + for e in self.g[s]: + self.add_flow(e, e.c) + + hi = 0 + while True: + while self.hs[hi].empty() + hi-- + if not (hi + 1): + return -self.ec[s] + u = self.hs[hi].pop() + while self.ec[u] > 0: // discharge u + if self.cur[u] == len(g[u]): + H[u] = 10**9 + for pos, e in enumerate(g[u]): + if e.c and self.H[u] > self.H[e.dest] + 1: + self.H[u] = self.H[e.dest] + 1 + self.cur[u] = pos; + co[self.H[u]] += 1 + co[hi] -= 1 + if (not co[hi]) and hi < v: + for i in range(v): + if hi < self.H[i] and self.H[i] < v: + co[self.H[i]] -= 1 + self.H[i] = v + 1 + hi = self.H[u] + else: + edge = self.g[self.cur[u]] + if edge.c and self.H[u] == self.H[edge.dest] + 1: + self.addFlow(edge, min(self.ec[u], edge.c)) + else cur[u] += 1 + + def leftOfMinCut(self, a): + return self.H[a] >= len(self.g) From c1faedd2ee04a98dc09c30f79917f1d8f41d76c3 Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 11:02:27 -0500 Subject: [PATCH 2/8] Long variable names --- pyrival/graphs/push_relabel.py | 82 +++++++++++++++++----------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 2b26e13..1ba7fbd 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -14,76 +14,78 @@ """ class Edge: - def __init__(self, dest, back, f, c): + def __init__(self, dest, back, flow, cap): self.dest = dest self.back = back - self.f = f - self.c = c + self.flow = flow + self.cap = cap class PushRelabel: def PushRelabel(self, n): - self.g = [[] for _ in range(n)] + self.graph = [[] for _ in range(n)] self.ec = [0]*n self.cur = [0]*n self.hs = [0]*(2*n) self.H = [[] for _ in range(n)] - def add_edge(self, s, t, cap, rcap=0): - if s == t: + def add_edge(self, src, dest, cap, rcap=0): + if src == dest: return - self.g[s].append(Edge(t, len(self.g[t]), cap, 0)) - self.g[t].append(Edge(s, len(self.g[s]) - 1, rcap, 0)) + self.graph[src].append( + Edge(dest, len(self.graph[dest]), cap, 0)) + self.graph[dest].append( + Edge(src, len(self.graph[src]) - 1, rcap, 0)) - def add_flow(self, e, f): - back = self.g[e.dest][e.back] - if (not self.ec[e.dest]) and f: - self.hs[self.H[e.dest]].append(e.dest) - e.f += f - e.c -= f - self.ec[e.dest] += f + def add_flow(self, edge, flow): + back = self.graph[edge.dest][edge.back] + if (not self.ec[edge.dest]) and flow: + self.hs[self.H[edge.dest]].append(edge.dest) + edge.flow += flow + edge.cap -= flow + self.ec[edge.dest] += flow - back.f -= f - back.c += f - self.ec[back.dest] -= f + back.flow -= flow + back.cap += flow + self.ec[back.dest] -= flow - def calc(s, t): - v = len(self.g) - self.H[s] = v - self.ec[t] = 1 - co = [0] * (2*v) - co[0] = v-1 - for i in range(v): + def calc(src, dest): + n = len(self.graph) + self.H[src] = n + self.ec[dest] = 1 + co = [0] * (2*n) + co[0] = n-1 + for i in range(n): self.cur[i] = 0 #.data() - for e in self.g[s]: - self.add_flow(e, e.c) + for edge in self.graph[src]: + self.add_flow(edge, edge.cap) hi = 0 while True: while self.hs[hi].empty() hi-- if not (hi + 1): - return -self.ec[s] + return -self.ec[src] u = self.hs[hi].pop() while self.ec[u] > 0: // discharge u - if self.cur[u] == len(g[u]): + if self.cur[u] == len(graph[u]): H[u] = 10**9 - for pos, e in enumerate(g[u]): - if e.c and self.H[u] > self.H[e.dest] + 1: - self.H[u] = self.H[e.dest] + 1 + for pos, edge in enumerate(graph[u]): + if edge.cap and self.H[u] > self.H[edge.dest] + 1: + self.H[u] = self.H[edge.dest] + 1 self.cur[u] = pos; co[self.H[u]] += 1 co[hi] -= 1 - if (not co[hi]) and hi < v: - for i in range(v): - if hi < self.H[i] and self.H[i] < v: + if (not co[hi]) and hi < n: + for i in range(n): + if hi < self.H[i] and self.H[i] < n: co[self.H[i]] -= 1 - self.H[i] = v + 1 + self.H[i] = n + 1 hi = self.H[u] else: - edge = self.g[self.cur[u]] - if edge.c and self.H[u] == self.H[edge.dest] + 1: - self.addFlow(edge, min(self.ec[u], edge.c)) + edge = self.graph[self.cur[u]] + if edge.cap and self.H[u] == self.H[edge.dest] + 1: + self.addFlow(edge, min(self.ec[u], edge.cap)) else cur[u] += 1 def leftOfMinCut(self, a): - return self.H[a] >= len(self.g) + return self.H[a] >= len(self.graph) From 50bb5f84b7e834852144fbfae80f26adcb5b6e2a Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 11:09:58 -0500 Subject: [PATCH 3/8] Edges as lists to avoid slow field access --- pyrival/graphs/push_relabel.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 1ba7fbd..e9b6f45 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -32,21 +32,22 @@ def add_edge(self, src, dest, cap, rcap=0): if src == dest: return self.graph[src].append( - Edge(dest, len(self.graph[dest]), cap, 0)) + [dest, len(self.graph[dest]), cap, 0]) self.graph[dest].append( - Edge(src, len(self.graph[src]) - 1, rcap, 0)) + [src, len(self.graph[src]) - 1, rcap, 0]) def add_flow(self, edge, flow): - back = self.graph[edge.dest][edge.back] - if (not self.ec[edge.dest]) and flow: - self.hs[self.H[edge.dest]].append(edge.dest) - edge.flow += flow - edge.cap -= flow - self.ec[edge.dest] += flow + dest = edge[0] + back = self.graph[dest][edge[1]] + if (not self.ec[dest]) and flow: + self.hs[self.H[dest]].append(dest) + edge[2] += flow + edge[3] -= flow + self.ec[dest] += flow - back.flow -= flow - back.cap += flow - self.ec[back.dest] -= flow + back[2] -= flow + back[3] += flow + self.ec[back[0]] -= flow def calc(src, dest): n = len(self.graph) @@ -57,7 +58,7 @@ def calc(src, dest): for i in range(n): self.cur[i] = 0 #.data() for edge in self.graph[src]: - self.add_flow(edge, edge.cap) + self.add_flow(edge, edge[3]) hi = 0 while True: @@ -70,8 +71,8 @@ def calc(src, dest): if self.cur[u] == len(graph[u]): H[u] = 10**9 for pos, edge in enumerate(graph[u]): - if edge.cap and self.H[u] > self.H[edge.dest] + 1: - self.H[u] = self.H[edge.dest] + 1 + if edge[3] and self.H[u] > self.H[edge[0]] + 1: + self.H[u] = self.H[edge[0]] + 1 self.cur[u] = pos; co[self.H[u]] += 1 co[hi] -= 1 @@ -83,8 +84,8 @@ def calc(src, dest): hi = self.H[u] else: edge = self.graph[self.cur[u]] - if edge.cap and self.H[u] == self.H[edge.dest] + 1: - self.addFlow(edge, min(self.ec[u], edge.cap)) + if edge[3] and self.H[u] == self.H[edge[0]] + 1: + self.addFlow(edge, min(self.ec[u], edge[3])) else cur[u] += 1 def leftOfMinCut(self, a): From 1b7453a37dfb7f3603259ae00af0aa003a5628da Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 11:21:02 -0500 Subject: [PATCH 4/8] Fix bug: .empty --- pyrival/graphs/push_relabel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index e9b6f45..7b2089a 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -24,7 +24,7 @@ class PushRelabel: def PushRelabel(self, n): self.graph = [[] for _ in range(n)] self.ec = [0]*n - self.cur = [0]*n + self.cur = [0]*n # Pointer to the next used edge self.hs = [0]*(2*n) self.H = [[] for _ in range(n)] @@ -56,13 +56,13 @@ def calc(src, dest): co = [0] * (2*n) co[0] = n-1 for i in range(n): - self.cur[i] = 0 #.data() + self.cur[i] = 0 for edge in self.graph[src]: self.add_flow(edge, edge[3]) hi = 0 while True: - while self.hs[hi].empty() + while not self.hs[hi]: hi-- if not (hi + 1): return -self.ec[src] From fac6f374c10ec6c1bc5a973c163ddb2d2cceec30 Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 11:22:56 -0500 Subject: [PATCH 5/8] Replace tabs with spaces --- pyrival/graphs/push_relabel.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 7b2089a..555c2af 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -41,19 +41,19 @@ def add_flow(self, edge, flow): back = self.graph[dest][edge[1]] if (not self.ec[dest]) and flow: self.hs[self.H[dest]].append(dest) - edge[2] += flow + edge[2] += flow edge[3] -= flow self.ec[dest] += flow - back[2] -= flow + back[2] -= flow back[3] += flow self.ec[back[0]] -= flow - + def calc(src, dest): - n = len(self.graph) + n = len(self.graph) self.H[src] = n self.ec[dest] = 1 - co = [0] * (2*n) + co = [0] * (2*n) co[0] = n-1 for i in range(n): self.cur[i] = 0 @@ -66,13 +66,13 @@ def calc(src, dest): hi-- if not (hi + 1): return -self.ec[src] - u = self.hs[hi].pop() + u = self.hs[hi].pop() while self.ec[u] > 0: // discharge u if self.cur[u] == len(graph[u]): - H[u] = 10**9 + H[u] = 10**9 for pos, edge in enumerate(graph[u]): if edge[3] and self.H[u] > self.H[edge[0]] + 1: - self.H[u] = self.H[edge[0]] + 1 + self.H[u] = self.H[edge[0]] + 1 self.cur[u] = pos; co[self.H[u]] += 1 co[hi] -= 1 @@ -81,12 +81,12 @@ def calc(src, dest): if hi < self.H[i] and self.H[i] < n: co[self.H[i]] -= 1 self.H[i] = n + 1 - hi = self.H[u] + hi = self.H[u] else: edge = self.graph[self.cur[u]] if edge[3] and self.H[u] == self.H[edge[0]] + 1: self.addFlow(edge, min(self.ec[u], edge[3])) else cur[u] += 1 - + def leftOfMinCut(self, a): return self.H[a] >= len(self.graph) From 495828dc9321f50522ffdaf3bfc67973265d0bcd Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 11:50:06 -0500 Subject: [PATCH 6/8] Global variables are self-less --- pyrival/graphs/push_relabel.py | 77 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 555c2af..7fef89d 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -20,73 +20,76 @@ def __init__(self, dest, back, flow, cap): self.flow = flow self.cap = cap +graph = [] +ec = [] +hs = [] +H = [] class PushRelabel: def PushRelabel(self, n): - self.graph = [[] for _ in range(n)] - self.ec = [0]*n - self.cur = [0]*n # Pointer to the next used edge - self.hs = [0]*(2*n) - self.H = [[] for _ in range(n)] + graph = [[] for _ in range(n)] + ec = [0]*n + hs = [0]*(2*n) + H = [[] for _ in range(n)] def add_edge(self, src, dest, cap, rcap=0): if src == dest: return - self.graph[src].append( - [dest, len(self.graph[dest]), cap, 0]) - self.graph[dest].append( - [src, len(self.graph[src]) - 1, rcap, 0]) + graph[src].append( + [dest, len(graph[dest]), cap, 0]) + graph[dest].append( + [src, len(graph[src]) - 1, rcap, 0]) def add_flow(self, edge, flow): dest = edge[0] - back = self.graph[dest][edge[1]] - if (not self.ec[dest]) and flow: - self.hs[self.H[dest]].append(dest) + back = graph[dest][edge[1]] + if (not ec[dest]) and flow: + hs[H[dest]].append(dest) edge[2] += flow edge[3] -= flow - self.ec[dest] += flow + ec[dest] += flow back[2] -= flow back[3] += flow - self.ec[back[0]] -= flow + ec[back[0]] -= flow def calc(src, dest): - n = len(self.graph) - self.H[src] = n - self.ec[dest] = 1 + n = len(graph) + H[src] = n + ec[dest] = 1 co = [0] * (2*n) co[0] = n-1 - for i in range(n): - self.cur[i] = 0 - for edge in self.graph[src]: + + cur = [0]*n # Pointer to the next used edge + for edge in graph[src]: self.add_flow(edge, edge[3]) hi = 0 while True: - while not self.hs[hi]: + while not hs[hi]: hi-- if not (hi + 1): - return -self.ec[src] - u = self.hs[hi].pop() - while self.ec[u] > 0: // discharge u - if self.cur[u] == len(graph[u]): + return -ec[src] + u = hs[hi].pop() + while ec[u] > 0: // discharge u + if cur[u] == len(graph[u]): H[u] = 10**9 for pos, edge in enumerate(graph[u]): - if edge[3] and self.H[u] > self.H[edge[0]] + 1: - self.H[u] = self.H[edge[0]] + 1 - self.cur[u] = pos; - co[self.H[u]] += 1 + if edge[3] and H[u] > H[edge[0]] + 1: + H[u] = H[edge[0]] + 1 + cur[u] = pos + co[H[u]] += 1 co[hi] -= 1 if (not co[hi]) and hi < n: for i in range(n): - if hi < self.H[i] and self.H[i] < n: - co[self.H[i]] -= 1 - self.H[i] = n + 1 - hi = self.H[u] + if hi < H[i] and H[i] < n: + co[H[i]] -= 1 + H[i] = n + 1 + hi = H[u] else: - edge = self.graph[self.cur[u]] - if edge[3] and self.H[u] == self.H[edge[0]] + 1: - self.addFlow(edge, min(self.ec[u], edge[3])) + edge = graph[cur[u]] + if edge[3] and H[u] == H[edge[0]] + 1: + self.addFlow(edge, min(ec[u], edge[3])) else cur[u] += 1 def leftOfMinCut(self, a): - return self.H[a] >= len(self.graph) + return H[a] >= len(graph) From 723027ef09d0cac57552c69b0dfe3abc8b3ff191 Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Fri, 25 Nov 2022 12:04:02 -0500 Subject: [PATCH 7/8] Move aux variables inside and cleanup --- pyrival/graphs/push_relabel.py | 54 ++++++++++++++-------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 7fef89d..2f921df 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -13,24 +13,9 @@ Date: 2022-11-25 """ -class Edge: - def __init__(self, dest, back, flow, cap): - self.dest = dest - self.back = back - self.flow = flow - self.cap = cap - graph = [] -ec = [] -hs = [] -H = [] class PushRelabel: - def PushRelabel(self, n): - graph = [[] for _ in range(n)] - ec = [0]*n - hs = [0]*(2*n) - H = [[] for _ in range(n)] - + # class Edge(dest, back, flow, cap) def add_edge(self, src, dest, cap, rcap=0): if src == dest: return @@ -39,27 +24,32 @@ def add_edge(self, src, dest, cap, rcap=0): graph[dest].append( [src, len(graph[src]) - 1, rcap, 0]) - def add_flow(self, edge, flow): - dest = edge[0] - back = graph[dest][edge[1]] - if (not ec[dest]) and flow: - hs[H[dest]].append(dest) - edge[2] += flow - edge[3] -= flow - ec[dest] += flow - - back[2] -= flow - back[3] += flow - ec[back[0]] -= flow - def calc(src, dest): n = len(graph) + + ec = [0]*n + cur = [0]*n # Pointer to the next used edge + hs = [0] * (2*n) + H = [0]*n + co = [0] * (2*n) + + def add_flow(self, edge, flow): + dest = edge[0] + back = graph[dest][edge[1]] + if (not ec[dest]) and flow: + hs[H[dest]].append(dest) + edge[2] += flow + edge[3] -= flow + ec[dest] += flow + + back[2] -= flow + back[3] += flow + ec[back[0]] -= flow + H[src] = n ec[dest] = 1 - co = [0] * (2*n) co[0] = n-1 - cur = [0]*n # Pointer to the next used edge for edge in graph[src]: self.add_flow(edge, edge[3]) @@ -70,7 +60,7 @@ def calc(src, dest): if not (hi + 1): return -ec[src] u = hs[hi].pop() - while ec[u] > 0: // discharge u + while ec[u] > 0: # discharge u if cur[u] == len(graph[u]): H[u] = 10**9 for pos, edge in enumerate(graph[u]): From fe9c2446100c70b95add86dd1b16ff365b1752d1 Mon Sep 17 00:00:00 2001 From: whosyourjay Date: Tue, 29 Nov 2022 09:13:30 -0500 Subject: [PATCH 8/8] Fix a couple more bugs --- pyrival/graphs/push_relabel.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pyrival/graphs/push_relabel.py b/pyrival/graphs/push_relabel.py index 2f921df..cddee4c 100644 --- a/pyrival/graphs/push_relabel.py +++ b/pyrival/graphs/push_relabel.py @@ -24,7 +24,7 @@ def add_edge(self, src, dest, cap, rcap=0): graph[dest].append( [src, len(graph[src]) - 1, rcap, 0]) - def calc(src, dest): + def calc(self, src, dest): n = len(graph) ec = [0]*n @@ -33,7 +33,7 @@ def calc(src, dest): H = [0]*n co = [0] * (2*n) - def add_flow(self, edge, flow): + def add_flow(edge, flow): dest = edge[0] back = graph[dest][edge[1]] if (not ec[dest]) and flow: @@ -51,12 +51,12 @@ def add_flow(self, edge, flow): co[0] = n-1 for edge in graph[src]: - self.add_flow(edge, edge[3]) + add_flow(edge, edge[3]) hi = 0 while True: while not hs[hi]: - hi-- + hi -= 1 if not (hi + 1): return -ec[src] u = hs[hi].pop() @@ -67,7 +67,7 @@ def add_flow(self, edge, flow): if edge[3] and H[u] > H[edge[0]] + 1: H[u] = H[edge[0]] + 1 cur[u] = pos - co[H[u]] += 1 + co[H[u]] += 1 co[hi] -= 1 if (not co[hi]) and hi < n: for i in range(n): @@ -79,7 +79,9 @@ def add_flow(self, edge, flow): edge = graph[cur[u]] if edge[3] and H[u] == H[edge[0]] + 1: self.addFlow(edge, min(ec[u], edge[3])) - else cur[u] += 1 - + else: + cur[u] += 1 + def leftOfMinCut(self, a): return H[a] >= len(graph) +