Back to Graph Theory Series

Circulation with Lower Bounds

October 11, 2026Wasil Zafar25 min read

When every route must carry a minimum commitment as well as respect a maximum capacity, feasibility becomes the problem before optimization begins.

Contents

  1. Why Lower Bounds Matter
  2. Model and Balance
  3. Super-Source Reduction
  4. Worked Example
  5. Bounded s–t Flow
  6. Why It Works
  7. Implementation
  8. Applications
  9. Diagnosing Failure
  10. Complexity and Use

Why Lower Bounds Change the Starting Point

Ordinary maximum flow starts from the all-zero solution. That works because every edge permits zero flow. A lower bound removes that convenience: if a contract requires at least two units on an edge, zero is illegal before the algorithm even begins.

IntuitionUpper capacity says a pipe cannot carry more than its diameter allows. A lower bound says the pipe must carry a promised minimum—perhaps to satisfy a contract, reserve bandwidth, or keep a service operating. Committing all promises may leave some junctions receiving more than they send and others sending more than they receive. The reduction asks whether the unused pipe capacity can repair those imbalances.

From flow to circulation

FlowA source injects material and a sink removes it.
CirculationThere is no distinguished source or sink; every vertex must balance.
BoundedEvery edge must remain between a required lower bound and an allowed upper bound.

Lower Bounds and the Balance Ledger

Each directed edge $(u,v)$ has lower bound $\ell(u,v)$ and upper bound $c(u,v)$:

$$\ell(u,v)\le f(u,v)\le c(u,v)$$

A circulation must also conserve flow at every vertex:

$$\sum_{(u,v)\in E}f(u,v)=\sum_{(v,w)\in E}f(v,w)$$

The reduction first commits every lower bound by writing $f(u,v)=\ell(u,v)+x(u,v)$. The new variable $x$ is the extra flow we may still choose, with capacity $0\le x(u,v)\le c(u,v)-\ell(u,v)$.

Define the committed imbalance using one fixed sign convention:

$$b(v)=\text{incoming lower flow}-\text{outgoing lower flow}$$
BalanceWhat lower bounds didWhat residual flow must doAuxiliary edge
$b(v)>0$Left excess committed inflowSend $b(v)$ more out$S^*\to v$ with capacity $b(v)$
$b(v)<0$Left a committed inflow deficitReceive $-b(v)$ more$v\to T^*$ with capacity $-b(v)$
$b(v)=0$Already balancedNo repair requiredNone

A free sanity check

Every committed edge subtracts its lower bound at one endpoint and adds the same amount at the other, so $\sum_v b(v)=0$. If your balance array does not sum to zero, the bookkeeping—not the network—is wrong.

Three-node circulation with lower and upper edge boundsA connects to B with bounds two and five, B connects to C with bounds one and four, and C connects to A with bounds zero and three. Committing lower bounds gives A balance negative two, B positive one, and C positive one. edge label = [lower, upper] · node badge = committed balance b [2, 5] [1, 4] [0, 3] ABC b = −2 b = +1 b = +1
Committing the lower bounds sends two units from A to B and one from B to C. A now needs two units of residual inflow; B and C must send one additional unit out each.

The Super-Source Reduction

Create a new source $S^*$ and sink $T^*$. Keep each original edge with residual capacity $c-\ell$, then connect the balance ledger to the two auxiliary vertices.

Feasibility Reduction
flowchart TD
    L[Commit every lower bound] --> B[Compute each balance b]
    B --> A[Add residual capacities and auxiliary edges]
    A --> F[Run max flow from S* to T*]
    F --> Q{Every S* edge saturated?}
    Q -->|Yes| Y[Feasible circulation]
    Q -->|No| N[No feasible circulation]
  1. For each bounded edge $(u,v,\ell,c)$, add an ordinary edge $u\to v$ of capacity $c-\ell$.
  2. Update $b(u)\mathrel{-}=\ell$ and $b(v)\mathrel{+}=\ell$.
  3. If $b(v)>0$, add $S^*\to v$ with capacity $b(v)$ and add that amount to the required auxiliary flow.
  4. If $b(v)<0$, add $v\to T^*$ with capacity $-b(v)$.
  5. Run max flow from $S^*$ to $T^*$. Feasibility is equivalent to sending the full required amount.

The test in one sentence

The auxiliary max flow tries to route every unit of excess committed inflow through unused original capacity until it reaches a vertex with a committed inflow deficit.

Worked Example: Repairing the Triangle

For the three-edge network above, the lower-bound ledger is:

VertexCommitted inflowCommitted outflow$b(v)$Auxiliary edge
A$0$$2$$-2$$A\to T^*$, capacity $2$
B$2$$1$$+1$$S^*\to B$, capacity $1$
C$1$$0$$+1$$S^*\to C$, capacity $1$
Transformed auxiliary max-flow networkA super-source sends one unit to B and one to C. One unit follows B to C to A, and the other follows C to A. A sends two units to the super-sink. All auxiliary edges are saturated. solid teal = repair flow · dashed crimson = saturated auxiliary edge · blue = unused residual capacity cap 1 cap 1 cap 3 · use 1 cap 3 · use 2 cap 3 · use 0 cap 2 S*BCAT* repair paths: S*→B→C→A→T* and S*→C→A→T*
The auxiliary flow has value 2 and saturates both edges leaving $S^*$. The residual repair sends one extra unit on $B\to C$ and two on $C\to A$.

Add the repair flow back to the committed lower bounds:

Original edgeLower-bound commitmentResidual repairFinal flowWithin bounds?
$A\to B$$2$$0$$2$Yes: $2\le2\le5$
$B\to C$$1$$1$$2$Yes: $1\le2\le4$
$C\to A$$0$$2$$2$Yes: $0\le2\le3$

The final circulation sends two units around the entire triangle. Every vertex now receives two and sends two, so conservation and all edge bounds hold simultaneously.

Turning a Bounded $s$–$t$ Flow into a Circulation

A normal $s$–$t$ flow intentionally violates conservation at two vertices: $s$ has net outflow and $t$ has net inflow. Close that accounting loop by adding an artificial edge $t\to s$ with lower bound $0$ and a safely large upper bound, then run the same circulation-feasibility reduction.

1. Close the loop

Add $t\to s$. Its flow represents the value transported from $s$ to $t$.

2. Find feasibility

Attach $S^*$ and $T^*$ from balances and saturate all required auxiliary flow.

3. Optimize if needed

Remove auxiliary vertices and the artificial edge, then augment in the appropriate residual direction.

For a maximum feasible bounded $s$–$t$ flow, record the current flow $F_0$ on the artificial $t\to s$ edge. Disable that edge and its paired reverse residual edge, remove $S^*$ and $T^*$, then run ordinary max flow from $s$ to $t$ on the remaining residual network. If that run adds $\Delta$, the answer is $F_0+\Delta$.

“Infinite” should still be finite

In code, use a proven upper bound such as the sum of relevant capacities, not the numeric maximum of the data type. A defensible finite bound avoids overflow in later arithmetic.

Why Saturation Proves Feasibility

The reduction preserves each requirement for a different reason:

Lower bounds

They were committed before max flow began and are added back during reconstruction.

Upper bounds

Extra flow uses capacity $c-\ell$, so commitment plus repair never exceeds $c$.

Conservation

Saturating every auxiliary requirement routes exactly enough repair flow to cancel every balance.

If the maximum auxiliary flow is smaller than $\sum_{b(v)>0}b(v)$, some committed excess cannot reach the deficits through residual capacity. No different arrangement can satisfy all lower bounds, upper bounds, and conservation: the max-flow/min-cut theorem supplies the obstruction.

Implementation: Build the Reduction Carefully

The reusable code is not a new max-flow algorithm. It is a wrapper that converts bounded edges into ordinary residual edges and a balance array, then calls an existing max-flow implementation.

def add_bounded_edge(graph, balance, u, v, lower, upper):
    if lower > upper:
        raise ValueError("lower bound exceeds upper bound")
    add_edge(graph, u, v, upper - lower)
    balance[u] -= lower
    balance[v] += lower

def attach_super_nodes(graph, balance, super_source, super_sink):
    required = 0
    for vertex, value in enumerate(balance):
        if value > 0:
            add_edge(graph, super_source, vertex, value)
            required += value
        elif value < 0:
            add_edge(graph, vertex, super_sink, -value)
    return required

# feasible iff max_flow(graph, super_source, super_sink) == required
using int64 = long long;

void addBoundedEdge(int u, int v, int64 lower, int64 upper,
                    vector<int64>& balance) {
    if (lower > upper) throw invalid_argument("invalid bounds");
    addEdge(u, v, upper - lower);
    balance[u] -= lower;
    balance[v] += lower;
}

int64 attachSuperNodes(const vector<int64>& balance,
                       int superSource, int superSink) {
    int64 required = 0;
    for (int v = 0; v < (int)balance.size(); ++v) {
        if (balance[v] > 0) {
            addEdge(superSource, v, balance[v]);
            required += balance[v];
        } else if (balance[v] < 0) {
            addEdge(v, superSink, -balance[v]);
        }
    }
    return required;
}
static void addBoundedEdge(int u, int v, long lower, long upper,
                           long[] balance) {
    if (lower > upper) throw new IllegalArgumentException("invalid bounds");
    addEdge(u, v, upper - lower);
    balance[u] -= lower;
    balance[v] += lower;
}

static long attachSuperNodes(long[] balance,
                             int superSource, int superSink) {
    long required = 0;
    for (int v = 0; v < balance.length; v++) {
        if (balance[v] > 0) {
            addEdge(superSource, v, balance[v]);
            required += balance[v];
        } else if (balance[v] < 0) {
            addEdge(v, superSink, -balance[v]);
        }
    }
    return required;
}

The snippets assume addEdge creates the usual paired forward and reverse residual edges. Keep the original lower bound alongside the residual edge so final flow can be reconstructed as $\ell+x$.

Implementation checklist

  • Reject every edge with $\ell>c$ before building the graph.
  • Choose and document one balance sign convention; never mix formulas from the opposite convention.
  • Use wide integers for capacities, balances, and their sums.
  • Compare achieved auxiliary flow with the sum of all $S^*$-edge capacities.
  • Recover each original flow by adding its lower bound to the used residual flow.
  • Exclude auxiliary and artificial edges from the final domain solution.

Minimum Commitments in Real Networks

Lower bounds represent promises, minimum service levels, or obligations; upper bounds represent physical or policy limits.

Distribution

Contracted Shipments

Customer lanes require minimum weekly volume while plants, warehouses, and roads impose upper limits.

Networks

Reserved Bandwidth

Service guarantees reserve minimum throughput while link capacity restricts the total routed traffic.

Workforce

Minimum Staffing

Shift and role edges encode mandatory coverage alongside maximum worker availability.

Finance

Cash-Flow Obligations

Required transfers and account limits can be tested for simultaneous feasibility before costs are optimized.

Feasibility answers only “can every commitment be honored?” If multiple feasible circulations have different prices, add costs and solve a minimum-cost circulation or flow after applying the same lower-bound transformation.

Diagnosing an Infeasible Instance

A failed max-flow run is useful evidence, not merely a boolean result. Vertices reachable from $S^*$ in the final residual graph define a cut: the original residual edges crossing that cut do not have enough capacity to transport all committed excess toward the required deficits.

Common sources of failure

  • An individual lower bound already exceeds its upper bound.
  • A group of vertices promises more outgoing flow than the rest of the network can return.
  • A required repair exists in the wrong direction because edges are directed.
  • The model omitted a legitimate route, duplicated a commitment, or used the wrong balance sign.

Test the worked example

Change the upper bound of $C\to A$ from $3$ to $1$. Does a feasible circulation remain?

Answer: no. After committing lower bounds, A still needs two units of residual inflow, but $C\to A$ can now carry only one extra unit and no other residual edge enters A.

Complexity, Limits, and When to Use It

The transformation adds exactly two vertices and at most one auxiliary edge per original vertex. With $V'=V+2$ and $E'=E+O(V)$, runtime is dominated by the chosen max-flow engine on the transformed graph. Reconstruction is linear in the number of original edges.

ProblemRecommended starting pointWhy
All lower bounds are zeroOrdinary max flowThe zero flow is already a feasible starting point.
Only feasibility with lower/upper boundsThis circulation reduction + DinicDirectly converts the problem to one max-flow run.
Feasibility plus linear per-unit costsMinimum-cost circulationChooses the cheapest solution after satisfying bounds.
Logical, nonlinear, or cross-edge constraintsLP/MIP or constraint solverOrdinary flow conservation cannot express every business rule.

Mental model to keep

Commit every promise, record who became over-supplied and under-supplied, then ask unused network capacity to rebalance the ledger. Saturating all super-source edges means every promise can coexist.