Jul 1, 2020
Ultimate Pit Limit Solver (Pseudoflow)
An interactive app that finds the optimal open-pit outline by turning the mine into a max-flow / min-cut problem.
Every open-pit mine starts with one deceptively simple question: which rocks should we dig up? Dig too little and you leave money in the ground; dig too much and you pay to move waste that never pays for itself. The economically optimal answer — the largest pit whose contained value exceeds the cost of reaching it, while respecting how steep the walls can safely be — is the ultimate pit limit (UPL).
Most mining engineers meet this problem as a 2-D textbook exercise (Lerchs–Grossmann by hand) and then spend their careers as users of black-box software. I wanted to build the real thing — solver and all.
Give every block a value
The orebody is discretized into a 3-D block model. Each block gets an economic value : positive if the metal it contains is worth more than the cost to mine and process it, negative if it is just waste you must move. With a cutoff grade separating ore from waste,
where are mining and processing cost, the metal price, the treatment charge, the recovery, and a depth-dependent increment. A block's value is then
for grade and tonnage .
The trick: it is a minimum cut
Here is the elegant part. Finding the maximum-value set of blocks subject to slope precedence is equivalent to a minimum cut on a graph — and by the max-flow/min-cut theorem, you can solve it with a max-flow algorithm.
Build a directed graph with a source and sink :
- connect with capacity for every ore block,
- connect with capacity for every waste block,
- connect with infinite capacity whenever block must be removed before block (the slope/precedence constraint).
Saturate the network with max flow; the blocks on the source side of the resulting min cut are the optimal pit.
import networkx as nx
G = nx.DiGraph()
SOURCE, SINK = "s", "t"
for i, block in enumerate(blocks):
v = block_value(block)
if v > 0:
G.add_edge(SOURCE, i, capacity=v) # ore: fed by the source
else:
G.add_edge(i, SINK, capacity=-v) # waste: drains to the sink
# slope precedence: mine a block only if the blocks above it are mined too
for i, parents in precedence(blocks):
for p in parents:
G.add_edge(i, p, capacity=float("inf"))
# the source side of the minimum cut is the optimal pit
cut_value, (pit, _rest) = nx.minimum_cut(G, SOURCE, SINK)
In the real solver I swap networkx's generic max-flow for Hochbaum's pseudoflow algorithm, which is purpose-built for exactly this min-cut structure and scales to millions of blocks. The precedence cone — which blocks sit "above" a given block — comes from a cKDTree ball query, with the wall angle controlling how wide the cone is:
| Wall angle | Pattern | Blocks above |
|---|---|---|
| ~45° | 1–5 | 5 |
| ~40° | 1–9 | 9 |
Making it tangible
The whole thing lives in an interactive Streamlit app: upload a block-model CSV (or use the bundled default), it cleans coordinate outliers, renders the 3-D block model and a grade–tonnage curve in Plotly, takes your economic parameters, and then draws the resulting pit in 3-D with its total undiscounted value.
The point was never just the answer — it was understanding the machine well enough to build it.
Stack: Python · pseudoflow · NetworkX · SciPy (cKDTree for slope precedence) · Plotly · Streamlit.