Planning Long-Term Civilization Survival
A growing population produces more, but it also consumes more. When some resources regenerate and others run out for good, and when droughts, earthquakes, and swings in demand arrive at random, the question becomes: how should a civilization plan so that its population after 100 turns is as large as possible, without ever dying out along the way?
In this article we build a concrete model, turn it into an optimization problem, and solve it with a vectorized evolutionary algorithm in Python.
1. The Example Problem
The civilization starts with a population $P_0 = 100$, a renewable stock $R_0 = 800$ (forests, fisheries, fertile soil), a finite stock $F_0 = 2000$ (ore, fossil fuel), and a resource efficiency $E_0 = 1$.
Each turn $t = 0, 1, \dots, 99$ the planner chooses three controls:
- $u_t \in [0, 0.30]$: the fraction of the renewable stock harvested
- $v_t \in [0, 0.06]$: the fraction of the finite stock mined
- $w_t \in [0, 0.50]$: the share of output invested in resource efficiency
To keep the search space small, the controls are piecewise constant over 10 blocks of 10 turns, which gives $3 \times 10 = 30$ decision variables.
Three random shocks hit each scenario:
- Drought: with probability 0.05 per turn, the renewable stock is multiplied by $0.55$.
- Earthquake: with probability 0.03 per turn, the population is multiplied by $0.80$ and efficiency is cut by 10%.
- Demand fluctuation: per-capita demand is multiplied by $\eta_t = 1 + 0.15\sin(2\pi t/25 + \phi) + 0.05,\xi_t$, where $\xi_t \sim \mathcal{N}(0,1)$.
2. Mathematical Model
Let $\delta_t$ and $q_t$ be the drought and earthquake multipliers (equal to 1 when no disaster occurs). After shocks, $R_t’ = \delta_t R_t$ and $P_t’ = q_t P_t$.
Renewable resource (logistic regrowth minus harvest):

Finite resource:
$$
M_t = v_t F_t, \qquad F_{t+1} = F_t - M_t
$$
Production (labor and resources, with diminishing returns):
$$
Y_t = A,(P_t’)^{\alpha},\left(H_t + \gamma M_t\right)^{1-\alpha}
$$
Demand and supply ratio:
$$
D_t = \frac{d_0,\eta_t,P_t’}{E_t}, \qquad s_t = \frac{(1 - w_t),Y_t}{D_t}
$$
Population growth (decline is faster than growth):
$$
g_t = \begin{cases} 0.05\tanh\bigl(3(s_t - 1)\bigr) & s_t \ge 1 \ 0.12\tanh\bigl(3(s_t - 1)\bigr) & s_t < 1 \end{cases}, \qquad P_{t+1} = P_t’,(1 + g_t)
$$
If $P_{t+1} < 1$, the civilization is extinct and the population is set to 0 permanently.
Efficiency growth:
The parameters are $r = 0.12$, $K = 1000$, $A = 1.35$, $\alpha = 0.6$, $\gamma = 1.5$, $d_0 = 1$, $\kappa = 0.05$, and $E_{\max} = 4$.
Optimization problem. Over $S$ random scenarios, with the controls $\boldsymbol{\theta} = (u_t, v_t, w_t)$ as decision variables, we solve

with $P_{\mathrm{safe}} = 30$ and $\lambda = 20$. The first term maximizes the expected final population. The second term is a penalty that forces the population to stay away from extinction in every scenario, not just on average.
3. Solution Strategy
The model is nonlinear, has hard constraints (extinction is absorbing), and contains random shocks, so gradient-based methods are a poor fit. We use differential evolution, a population-based global optimizer.
The simulation of 100 turns is cheap, but an evolutionary algorithm needs tens of thousands of evaluations. The code below is therefore written to be fast:
- The simulator is vectorized over candidates and scenarios simultaneously, so one call evaluates a whole generation of 360 candidate plans on 40 scenarios with only 100 loop iterations.
- SciPy’s
vectorized=Trueandupdating="deferred"options pass the entire population to the objective function at once. - Plans are optimized on 40 training scenarios and then validated on 400 unseen scenarios, so the reported performance is out-of-sample.
4. Source Code
1 | import time |
5. Code Walkthrough
Section 1: problem setting. All model constants are defined once at the top. make_scenarios draws, for every scenario, a drought multiplier array, an earthquake multiplier array, and a demand multiplier array, each of shape (n, T). A fixed random seed makes the experiment reproducible. Two independent scenario sets are created later: 40 for training and 400 for testing.
Section 2: the vectorized simulator. simulate is the heart of the program. The decision vector theta has 30 entries in $[0,1]$: the first 10 are harvest intensities, the next 10 mining intensities, and the last 10 investment shares. They are scaled by U_MAX, V_MAX, and W_MAX and expanded to one value per turn with np.repeat.
State variables P, R, F, and E are arrays of shape (M, S): $M$ candidate plans times $S$ scenarios. The time loop applies the shocks, harvests and mines, computes production $Y$, demand $D$, and the supply ratio $s$, and then updates every stock. Because a single loop advances all candidates and all scenarios at once, the cost per generation is nearly independent of population size. The line P = np.where(P < 1.0, 0.0, P) makes extinction absorbing, and min_p tracks the lowest population ever reached in each run. With record=True the full histories are kept for plotting.
Section 3: the objective and the optimizer. objective receives an array of shape (D, M), which is the format SciPy uses when vectorized=True, so it is transposed before simulating. It returns the negative mean final population plus the survival penalty, because SciPy minimizes. Differential evolution uses popsize=12, so each generation contains $12 \times 30 = 360$ candidate plans, and it runs for 300 generations. updating="deferred" is required for vectorized evaluation. The whole search takes only seconds, whereas a plain Python loop over every candidate and scenario would be orders of magnitude slower.
Baselines. Two hand-made policies serve as a reference. The greedy policy always harvests and mines at the maximum rate and never invests in efficiency. The conservative policy harvests and mines at low rates and invests a small constant share.
Section 4: the console report. The optimized plan and both baselines are evaluated on the 400 unseen scenarios. The table reports the mean, median, and 5th percentile of the final population, the extinction rate, and the share of scenarios in which the population dipped below the safety level of 30. The optimized control schedule is printed block by block.
Section 5: the policy landscape. To visualize how sensitive the outcome is to the two resource-extraction controls, we evaluate a $26 \times 26$ grid of constant harvest rates $u$ and mining rates $v$, holding the optimized investment schedule fixed. Each grid point is one more row in the same vectorized call.
Section 6: the combined figure. All six panels are drawn into one figure. The panels show the population bands for the three policies, the resource stocks under the optimized plan, the control schedule, the 3D landscape of final population against $(u, v)$, the 3D trajectory in $(P, R, F)$ space, and the box plots of final population on unseen scenarios.
6. Execution Results
Console Output
Optimization time: 35.6 s (300 generations) Best training objective: 793.55 Policy Mean P(100) Median 5th pct Extinct % Min<30 % Greedy 0.2 0.0 0.0 82.8 100.0 Conservative 64.1 60.1 24.2 0.0 20.0 Optimized 846.8 833.7 381.8 0.0 5.8 Optimized schedule per 10-turn block Block Harvest u Mining v Invest w 1-10 0.098 0.0029 0.499 11-20 0.043 0.0246 0.492 21-30 0.051 0.0218 0.493 31-40 0.055 0.0251 0.493 41-50 0.042 0.0278 0.436 51-60 0.045 0.0267 0.353 61-70 0.031 0.0315 0.272 71-80 0.042 0.0333 0.152 81-90 0.038 0.0566 0.000 91-100 0.153 0.0597 0.001
Graph Output

7. Interpreting the Results
Population trajectories. The greedy plan looks attractive at first: harvesting and mining at full speed produces a short boom in population. But the renewable stock collapses below the level where it can regenerate, the finite stock is burned through, and the population falls toward zero. In the large majority of unseen scenarios the greedy civilization goes extinct. The conservative plan never collapses, but it never grows either: the population stagnates at a low level because nothing is invested in efficiency, and a noticeable share of scenarios still dip below the safety level after a disaster. The optimized plan first dips slightly, then grows slowly, and finally accelerates sharply. Its final population is many times larger than the conservative one, with no extinctions in the test set.
Resource stocks. Under the optimized plan the renewable stock is quickly drawn down from its initial value to a moderate level and then held steady in a band that allows strong regeneration, close to the maximum-sustainable-yield point of the logistic curve. This is the signature of sustainable management: harvest the flow, not the stock. The finite stock declines smoothly and is deliberately spent over the whole horizon, ending near exhaustion at turn 100.
Control schedule. The schedule has a clear three-phase structure.
- Investment phase (early turns). Mining is almost off, harvesting is modest, and the investment share $w_t$ sits at its upper bound. Output is sacrificed to build efficiency $E_t$, which lowers the per-capita demand $D_t$ for the rest of the game.
- Steady phase (middle turns). Harvesting stays near the sustainable level, mining rises gradually, and investment is phased out as efficiency approaches its cap.
- Drawdown phase (final turns). With the horizon about to end, there is no reason to preserve stock for the future. Investment drops to zero, mining reaches its maximum, and harvesting jumps. This is the classic end-of-horizon effect in finite-horizon optimal control.
3D landscape. The surface of mean final population against constant $(u, v)$ is far from flat. Pushing the harvest rate too high creates a deep valley: the renewable stock is destroyed and the civilization collapses. Pushing it too low wastes production. The good region is a narrow ridge, and the optimizer has to find it in a 30-dimensional space, where an exhaustive grid search would be hopeless.
3D trajectory. In $(P, R, F)$ space, the greedy trajectory dives toward the zero-population axis after a short excursion. The conservative trajectory barely moves in population. The optimized trajectory first loses renewable stock to a sustainable level, then climbs steadily in population while the finite stock is consumed, and ends at a high population.
Robustness on unseen scenarios. The box plots confirm that the result is not an artifact of the 40 training scenarios. On 400 fresh scenarios the optimized plan keeps a clearly higher median final population than the alternatives, with a zero extinction rate. The penalty term $\lambda \sum_s \max(0, P_{\mathrm{safe}} - \min_t P_t^{(s)})$ is what buys this safety: it makes plans that gamble on avoiding disasters unattractive, even when their average outcome would look good.
8. Conclusion
We turned a vague question about civilization survival into a concrete optimization problem with renewable and finite resources, population-dependent production, and random disasters. A vectorized simulator combined with differential evolution found a plan that is both ambitious (a final population far above the cautious baseline) and safe (no extinction on unseen scenarios). The structure of the solution is intuitive: invest early in efficiency, harvest the renewable resource at its sustainable rate, spend the finite resource steadily, and draw everything down only when the horizon ends.
The same framework extends naturally to richer models: more resource types, age-structured populations, finer control blocks, or risk measures such as conditional value-at-risk in place of the simple survival penalty.









