How Much Ore Should You Dig?
Ore deposits are finite. Dig too slowly and the colony never grows. Dig too fast and the mine runs dry while people still depend on it. Selling ore buys food and machines. Food keeps people alive and growing. Machines raise the extraction capacity. This post turns that trade-off into a concrete optimization problem and solves it in Python.
1. The Problem
A colony lives on a single ore deposit. Each year it decides:
- how many tons to extract ($E$),
- how much of the revenue to reinvest in machines (the rest buys food).
The goal is to maximize the population at the end of year $T$ without wasting the deposit or running it dry too early.
2. Mathematical Model
Let $t = 0, 1, \dots, T-1$ be the year, $R_t$ the remaining ore, $K_t$ the number of machines and $P_t$ the population.
Extraction is limited by the target, the remaining ore and the machine capacity:
$$
e_t = \min\left(E,; R_t,; c,K_t\right)
$$
Revenue and its split between reinvestment and food:
$$
\text{rev}_t = p,e_t, \qquad I_t = s_t,\text{rev}_t, \qquad F_t = (1-s_t),\text{rev}_t
$$
The reinvestment share is $s_t = s$ during the first $\tau$ years and $s_t = 0$ afterwards (all revenue goes to food).
State transitions:
$$
R_{t+1} = R_t - e_t, \qquad K_{t+1} = (1-\delta)K_t + \frac{I_t}{m}
$$
Food sufficiency and population growth:
$$
z_t = \frac{F_t}{n,P_t}, \qquad P_{t+1} = P_t\left(1 + 2g_{\max},\operatorname{clip}(z_t - 1,,-1,,0.5)\right)
$$
With $z_t = 1$ the population is stable, $z_t = 0$ means starvation ($-10%$ per year) and $z_t \ge 1.5$ gives the maximum growth $+g_{\max}$.
Optimization problem:
$$
\max_{E,,s,,\tau}; P_T \quad \text{s.t.}\quad \sum_{t} e_t \le R_0,;; 0 \le s \le 1,;; 0 \le \tau \le T
$$
The deposit constraint $\sum_t e_t \le R_0$ is enforced automatically by $e_t \le R_t$.
Parameters used in this example:
| Symbol | Meaning | Value |
|---|---|---|
| $T$ | Horizon | 30 years |
| $R_0$ | Initial ore | 1000 t |
| $P_0$ | Initial population | 500 |
| $K_0$ | Initial machines | 10 |
| $p$ | Ore price | 40 |
| $c$ | Capacity per machine | 2 t/year |
| $m$ | Machine price | 400 |
| $\delta$ | Depreciation | 5% |
| $n$ | Food need per person | 1 |
| $g_{\max}$ | Max growth rate | 5% |
3. Solution Strategy
The decision space $(E, s, \tau)$ has only three dimensions, so the problem is solved in two stages:
- Exhaustive search over a grid of $71 \times 51 \times 31 = 112{,}251$ scenarios. All scenarios are simulated at the same time with NumPy broadcasting, so the year loop runs only 30 times instead of $30 \times 112{,}251$ times.
- Continuous refinement of the best grid point with Nelder–Mead.
The optimum is then compared with three baseline policies:
- (a) never reinvest,
- (b) always reinvest 80% of revenue,
- (c) over-mine with $E = 80$ t/year.
4. Source Code
1 | import time |
5. Code Walkthrough
5.1 Parameters and the simulate function
The constants at the top mirror the table in Section 2. The core is simulate, which runs the 30-year economy forward in time.
np.broadcast_arraysmakesE,sandtaushare one shape. Passing arrays of shapes(71,1,1),(1,51,1)and(1,1,31)therefore evaluates the whole $71\times51\times31$ cube at once. This is the speed-up: the Python loop runs over the 30 years only, and each iteration is a NumPy operation over 112,251 scenarios.ore = min(E, R, CAP*K)implements $e_t=\min(E,R_t,cK_t)$. The termRguarantees that no more ore than the deposit holds can ever be extracted, so the deposit constraint is always satisfied.np.where(t < tau, s, 0.0)switches reinvestment off after year $\tau$, which is the “invest first, harvest later” structure.- The growth rate
2*GMAX*clip(z-1, -1, 0.5)gives $-10%$ at $z=0$, $0%$ at $z=1$, and saturates at $+5%$ for $z\ge1.5$. Food beyond $1.5$ times the need is wasted, which is why over-feeding is never optimal. - With
record=True, the function stores the yearly trajectories of every state and returns them for plotting. Without it, only the final population array is returned, which keeps the grid search cheap.
5.2 Exhaustive search
PF is a three-dimensional array whose entry PF[i, j, k] is the final population for E_grid[i], s_grid[j] and tau_grid[k]. np.argmax followed by np.unravel_index recovers the best triple. Because the decision space is small, this brute-force approach is guaranteed to find the global optimum of the grid and, unlike a gradient-based method, is not troubled by the non-smooth min and clip operations.
5.3 Continuous refinement
The grid has finite resolution, so scipy.optimize.minimize with Nelder–Mead polishes the best grid point. Nelder–Mead needs no gradients, which suits this piecewise-defined model. The np.clip calls inside objective keep the search inside the feasible box $0\le s\le1$, $0\le\tau\le T$.
5.4 Baseline policies
The three baselines show what the optimization is worth:
- (a) No reinvestment: the best $E$ with $s=0$. The colony never expands its machine fleet.
- (b) Reinvest 80% always: the colony pours almost everything into equipment for the whole horizon and starves its own people.
- (c) Over-mining: the extraction target is set to the maximum of the grid ($80$ t/year), and only $s$ and $\tau$ are optimized.
5.5 Console report
The report prints the number of scenarios and the time taken, the grid optimum, the refined optimum, and a comparison table of final population and ore left for all four policies.
6. Console Output
======================================================================
Scenarios evaluated : 112,251 (vectorised, 0.087 s)
Grid optimum : E=44.0, s=0.46, tau=21, P_T=1080.0
Refined optimum : E=45.35 t/yr, s=0.459, tau=21.00 yr
P_T=1082.0, ore left=0.5 t, machines=14.9
----------------------------------------------------------------------
Policy Final pop. Ore left [t]
Optimal 1082.0 0.5
(a) No reinvestment 296.6 686.9
(b) Reinvest 80% always 287.0 1.7
(c) Over-mining (E=80) 1077.5 0.8
======================================================================
How to Read the Console Output
In my test run, the whole $112{,}251$-scenario search finished in a fraction of a second, and the refined optimum was close to
$$
E^* \approx 45.4\ \text{t/year}, \qquad s^* \approx 0.46, \qquad \tau^* \approx 21\ \text{years}, \qquad P_T \approx 1{,}082 .
$$
Almost no ore is left at the end ($\approx 0.5$ t), so the deposit is used up almost exactly at the horizon. The machine fleet grows from 10 to about 15 units.
The comparison table shows the following:
- No reinvestment ends at roughly $297$ people. The fleet decays through depreciation and the capacity $cK_t$ falls below the ore flow needed, so about $687$ t of ore stays in the ground unsold. This is the opposite failure to depletion: the resource is wasted.
- Reinvesting 80% always ends at roughly $287$ people. The ore is used up, but the colony spent too much on machines, starved, and never recovered.
- Over-mining with $E=80$ reaches about $1{,}077$, almost as good as the optimum. The reason is that the real throttle is the machine capacity, not the target $E$: once $E$ exceeds $cK_t$, raising it changes nothing. What separates good from bad policies is therefore the timing and size of the investment, not the extraction target alone.
7. Graphs

How to Read the Graphs
Top left: 3D surface of $E$ and $s$ (at the optimal $\tau$). The surface is a ridge rather than a sharp peak. For small $E$, the colony sells too little ore and the final population is low whatever $s$ is. For intermediate $E$ there is a clear maximum in $s$: with too little reinvestment, capacity is the bottleneck, and with too much, food runs short. The red dot marks the refined optimum sitting on top of the ridge.
Top center: 3D surface of $E$ and $\tau$ (at the optimal $s$). This shows the role of the investment window. Too short a window leaves the fleet too small to mine the whole deposit in time. Too long a window starves the colony because revenue goes to machines instead of food. The best $\tau$ lies in the second half of the horizon, so the colony invests for most of its life and switches to pure food spending near the end.
Top right: heatmap of final population. This is the top view of the first surface. The bright region is narrow along the $s$ axis and wide along the $E$ axis. In other words, the result is sensitive to the reinvestment share but forgiving about the extraction target, because capacity limits extraction anyway. The red star marks the optimum.
Bottom left: ore reserve. The optimal curve (red) descends steadily and touches zero right at year 30. The no-reinvestment curve (blue) stays high, which shows unused wealth. The over-mining curve (orange) drains the deposit fast, but only as fast as machine capacity allows, so it behaves much like the optimum.
Bottom center: population trajectory. The optimal population dips or stagnates early, because part of the revenue goes to machines, and then grows faster later as the larger fleet raises revenue. This is the classic invest now, benefit later pattern. The always-reinvest policy (green) collapses because the colony starves, and the no-reinvestment policy (blue) stalls because capacity decays.
Bottom right: allocation of revenue under the optimal policy. Each bar is one year’s revenue, split into food (green) and equipment (purple). The purple part is present for roughly the first 21 years and vanishes afterwards, which is exactly the switching structure of the optimum. The bars also grow over time as the machine fleet expands.
8. Conclusion
- A finite deposit turns the colony’s problem into a timing problem: what matters is when to invest and when to switch to consumption.
- The optimum extracts the deposit almost exactly as the horizon ends. It leaves no ore unused, and it does not exhaust the mine early and starve the people.
- Both extremes are bad. Too little reinvestment wastes the resource, and too much starves the population.
- Because the decision space is small, vectorized brute force plus a Nelder–Mead polish is fast and finds the global optimum without worrying about non-smooth model terms.
For richer variants, one can let the population supply the mining labor, make the ore price depend on extraction, or replace the single switching time $\tau$ with a fully time-dependent share $s_t$ optimized by dynamic programming.
















