Skip to content

Example 11: Maximum flow ​

1. Overview ​

This bounded context maximizes integral flow from root node 0 to sink node 8 in a directed capacity network. The Kotlin and Rust implementations use different registration details but encode the same edge set, balances, and objective.

The current network has these edge capacities:

EdgeCapacityEdgeCapacity
0→1150→210
0→3401→415
2→5102→635
3→6303→720
4→6105→810
6→7107→845

1. Dependent Contexts ​

  1. None. The network-flow model is self-contained.

2. Concepts / Entities ​

1. Node ​

A node is a network vertex. The sample has one root, seven normal nodes, and one sink.

r: root/source node, node 0.

t: end/sink node, node 8.

VN: normal nodes, all nodes other than r and t.

2. Directed Edge ​

An edge is a directed connection with a finite capacity.

cij: capacity of edge (i,j), defined only for modeled edges.


3. Variables ​

1. Decision Variables ​

xij: integral flow on edge (i,j), measured in flow units, domain Z≥0, ∀(i,j)∈E. Kotlin registers only entries present in capacities; Rust registers the full matrix and fixes non-edges to zero ranges.

f: total source-to-sink flow, measured in flow units, domain mathbbZ≥0. Kotlin uses scalar UIntVar("flow"); Rust uses a one-element VariableCombination1D<UInteger>.

2. Auxiliary Variables ​

Ini and Outi are registered linear intermediate symbols derived from edge flows. They are not independent decisions.


4. Predicates ​

1. Network Membership Predicates ​

IsEdge(i,j): true when (i,j)∈E and the capacity map has an entry.

IsNormal(i): true when i∈V∖{r,t}.


5. Sets ​

1. Node and Edge Categories ​

V: universal node set, V={0,1,…,8}.

E: exactly the twelve directed edges listed in the data table.

VN: normal-node subset, VN=V∖{r,t}.

2. Entity Pairs / Relations ​

NetworkEdge: relation E connecting a tail node to a head node.

Non-edges (i,j)∉E have no business flow variable in Kotlin and a fixed-zero matrix item in Rust.


6. Intermediate Values ​

1. Inflow at a Node ​

Description: Total flow entering node i along modeled incoming edges.

Ini=∑j:(j,i)∈Exji,∀i∈V.

2. Outflow at a Node ​

Description: Total flow leaving node i along modeled outgoing edges.

Outi=∑j:(i,j)∈Exij,∀i∈V.

3. Balance Residual ​

Description: The difference between outflow and inflow; it equals f at the root, −f at the sink, and zero at normal nodes.

Balancei=Outi−Ini={f,i=r,−f,i=t,0,i∈VN.

7. Assertions ​

1. Capacity Dominates Edge Flow ​

Description: No modeled edge carries more flow than its capacity.

∀(i,j)∈E(0≤xij≤cij).

2. Normal-Node Conservation ​

Description: A normal node neither creates nor consumes net flow.

∀i∈VN(Outi=Ini).

3. Root/Sink Flow Agreement ​

Description: The same non-negative scalar f leaves the root and enters the sink.

Outr−Inr=f∧Int−Outt=f∧f≥0.

4. Sample Maximum ​

Description: For the supplied network the current model has maximum flow f=40. One feasible witness sends 10 on 0→1→4→6→7→8, 10 on 0→2→5→8, and 20 on 0→3→7→8; the remaining edge flows are zero. The incoming capacity to node 7 limits the usable 7→8 flow to 30, so the witness is optimal.

f∗=40.

8. Constraints ​

1. Edge Capacity (边容量约束) ​

Description: Each modeled edge flow is non-negative and cannot exceed the capacity. Kotlin imposes the upper bound while registering each edge item; Rust encodes it in the range generator.

s.t.0≤xij≤cij,∀(i,j)∈E.

2. Root Balance (源点流量平衡约束) ​

Description: Net flow leaving the root equals the total flow variable.

s.t.Outr−Inr=f.

3. Sink Balance (汇点流量平衡约束) ​

Description: Net flow entering the sink equals the same total flow variable.

s.t.Int−Outt=f.

4. Normal-Node Conservation (中间节点守恒约束) ​

Description: Every normal node has equal inflow and outflow. Kotlin adds both inequalities; Rust adds one Equal relation after combining the two expressions.

s.t.Outi=Ini,∀i∈VN.

Implementation forms:

s.t.Outi≥Ini ∧ Outi≤Ini,qquad∀i∈VN(Kotlin),

or one equality in Rust.

5. Flow Domain (总流量定义域约束) ​

Description: Total flow is an integral non-negative quantity.

s.t.f∈Z≥0.

9. Objective Function (if applicable) ​

Description: Maximize the total flow delivered from root to sink.

maxf.

10. Algorithm References ​

No standalone algorithm document is referenced. This is the standard capacitated maximum-flow linear model.

Algorithm NameFile PathReferenced InBrief Description
None——The page documents the optimization formulation directly.

11. Ubiquitous Language ​

TermSymbolDefinition
Root/sourcerNode 0 where flow originates.
Sink/endtNode 8 where flow terminates.
Edge flowxijIntegral flow on a modeled directed edge.
CapacitycijUpper bound on an edge flow.
InflowIniSum of incoming edge flows.
OutflowOutiSum of outgoing edge flows.

12. Design Decisions ​

DecisionAlternativesRationaleDate
Keep only the twelve listed edges as business edgesAdd variables for every node pairThe source capacity map defines the graph; absent entries are not edges2026-09-08
Express normal conservation as equalityKeep only one inequality directionKotlin's two inequalities and Rust's combined equality have the same semantics2026-09-08
Use an explicit total flow variableMaximize root outflow directlyBoth current sources expose flow as a model variable2026-09-08

Minimal current model-building snippets ​

The node and capacity data come from Demo11.kt/demo11.rs. The tabs include variables, inflow/outflow expressions, objective, and all user-level balance constraints; range registration carries edge capacities.

kotlin
// `nodes`, `capacities`, `rootNode`, `endNode`, and `flt64Converter` come from Demo11.kt.
val metaModel = LinearMetaModel<Flt64>("demo11", converter = flt64Converter)
val x = UIntVariable2("x", Shape2(nodes.size, nodes.size))
for (from in nodes) for (to in nodes) {
    capacities[from]?.get(to)?.let { capacity ->
        x[from, to].range.leq(capacity)
        metaModel.add(x[from, to])
    }
}
val flow = UIntVar("flow")
metaModel.add(flow)
val flowIn = LinearIntermediateSymbols1<Flt64>("flow_in", Shape1(nodes.size)) { i, _ ->
    LinearExpressionSymbol(sum(x[_a, i]), name = "flow_in_$i")
}
val flowOut = LinearIntermediateSymbols1<Flt64>("flow_out", Shape1(nodes.size)) { i, _ ->
    LinearExpressionSymbol(sum(x[i, _a]), name = "flow_out_$i")
}
metaModel.add(flowIn)
metaModel.add(flowOut)
metaModel.maximize(flow, "flow")
metaModel.addConstraint(flowOut[rootNode] - flowIn[rootNode] eq flow)
metaModel.addConstraint(flowIn[endNode] - flowOut[endNode] eq flow)
for (node in nodes.filterIsInstance<NormalNode>()) {
    metaModel.addConstraint(flowOut[node] geq flowIn[node])
    metaModel.addConstraint(flowOut[node] leq flowIn[node])
}
rust
// `data` comes from MaxFlowData::sample() in demo11.rs.
let node_count = data.nodes.len();
let mut model = MetaModel::<f64>::new("demo11");
let arc_vars = VariableCombination2D::<UInteger>::with_name_and_range_generator(
    Shape::new([node_count, node_count]), "x",
    |_index, vector| format!("{}_{}", vector[0], vector[1]),
    |_index, vector| data.capacities.iter()
        .find(|arc| arc.from == vector[0] && arc.to == vector[1])
        .map(|arc| VariableRange::bounded(0.0, arc.capacity))
        .unwrap_or_else(|| VariableRange::fixed(0.0)),
);
let arc_idx = model.register_combination(&arc_vars)?;
let flow_vars = VariableCombination1D::<UInteger>::new(Shape::new([1]), "flow");
let flow_idx = model.register_combination(&flow_vars)?;
model.add_linear_objective(&[(flow_idx[0], 1.0)], "flow");
model.set_objective_category(ObjectiveCategory::Maximum);
let flow_out = flat_map1_indexed("flow_out", &data.nodes, |node, _| {
    let monomials = (0..node_count).map(|j|
        ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, arc_idx[&[node, j]])
    ).collect();
    ospf_rust_core::symbol::flatten::Linear::new(monomials, 0.0)
}, |i, _| format!("{}", i));
let flow_in = flat_map1_indexed("flow_in", &data.nodes, |node, _| {
    let monomials = (0..node_count).map(|i|
        ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, arc_idx[&[i, node]])
    ).collect();
    ospf_rust_core::symbol::flatten::Linear::new(monomials, 0.0)
}, |i, _| format!("{}", i));
model.add_symbol_combination(&flow_out)?;
model.add_symbol_combination(&flow_in)?;
// add_constraints() combines `flow_out - flow_in`, adds `-flow` at root,
// `+flow` at sink, and an Equal 0 balance for each normal node.

13. Change Log ​

VersionChangeReason
2026-09-08Reorganized the page into the domain-model template; added quantified intermediate values, bilingual constraint names, and equivalent Kotlin/Rust tabsMatch the current Demo11 implementations and preserve their conservation-form difference

Source and verification ​