A Julia library for mathematical modeling and statistical analysis of historical dynamics.
Cliodynamics is the scientific study of historical dynamics — applying mathematical models and quantitative methods to understand long-term patterns in social complexity, state formation, demographic cycles, elite dynamics, and political instability. This package implements frameworks from Peter Turchin’s research program in cliodynamics.
Cliodynamics treats history as a science, using mathematical models to analyze:
-
Population dynamics and demographic pressures
-
Elite overproduction and intra-elite competition
-
Political instability and state breakdown
-
Secular cycles (150-300 year oscillations in societies)
-
State capacity and collective action problems
The field bridges history, mathematics, evolutionary theory, and complex systems science.
-
Malthusian Models: Logistic population growth with carrying capacity constraints
-
Demographic-Structural Theory (DST): Coupled models of population, elites, and state capacity
-
Population Pressure: Measurement of stress relative to carrying capacity
-
Elite Overproduction Index: Quantify when elite supply exceeds available positions
-
Elite-to-Population Ratios: Track elite expansion relative to general population
-
Intra-Elite Competition: Model competition effects on political stability
-
Political Stress Indicator (PSI): Composite measure combining:
-
Mass Mobilization Potential (wage decline, immiseration)
-
Elite Mobilization Potential (elite overproduction)
-
State Fiscal Distress (revenue crisis)
-
-
Instability Probability: Convert stress indicators to event probabilities
-
Conflict Intensity: Aggregate historical instability events over time
-
Cycle Detection: Identify 150-300 year oscillations in historical data
-
Phase Classification: Categorize periods as Expansion, Stagflation, Crisis, or Depression
-
Trend-Cycle Decomposition: Separate long-term trends from cyclical components
-
State Capacity Models: Tax revenue, military strength, institutional quality
-
Collective Action Problems: Model cooperation challenges in state building
-
Institutional Development: Track state-building trajectories
-
Instability Diffusion: Multi-region model of instability spreading across borders
-
Territorial Competition: Lotka-Volterra style state competition over territory
-
Frontier Formation: Meta-ethnic frontier index (Turchin’s frontier thesis)
-
Malthusian Fitting: Recover growth rate and carrying capacity from observed population data
-
DST Fitting: Fit full demographic-structural model to historical time series
-
Parameter Estimation: Generic model fitting with bootstrap confidence intervals
-
Seshat Integration: Load and analyze data from the Seshat Global History Databank
Requires using Turing (loaded as package extension):
-
Bayesian Malthusian: MCMC posterior estimation for growth parameters
-
Bayesian DST: Full Bayesian fit of demographic-structural model
-
Model Comparison: WAIC-based model selection
Plot recipes for Plots.jl via package extension (loaded automatically when using Plots):
-
plot(psi_result, Val(:psi))— PSI with component breakdown -
plot(eoi_result, Val(:eoi))— Elite overproduction with baseline -
plot(analysis, Val(:secular_cycle))— Trend-cycle decomposition -
plot(phases, Val(:phases))— Phase-colored timeline -
plot(intensity, Val(:conflict))— Conflict intensity
using Cliodynamics
using DataFrames
# 1. Model Malthusian population dynamics
params = MalthusianParams(r=0.02, K=1000.0, N0=100.0)
sol = malthusian_model(params, tspan=(0.0, 200.0))
println("Population at t=200: ", round(sol(200.0)[1], digits=1))
# 2. Calculate elite overproduction index
data = DataFrame(
year = 1800:1900,
population = collect(100_000:1000:200_000),
elites = [1000 + 10*i + 5*i^1.5 for i in 0:100]
)
eoi = elite_overproduction_index(data)
println("Final EOI: ", round(eoi.eoi[end], digits=3))
# 3. Calculate Political Stress Indicator
stress_data = DataFrame(
year = 1800:1900,
real_wages = 100.0 .- collect(0:100).^1.2 ./ 10,
elite_ratio = 0.01 .+ collect(0:100) ./ 5000,
state_revenue = 1000.0 .- collect(0:100).^1.5 ./ 5
)
psi = political_stress_indicator(stress_data)
println("PSI at 1900: ", round(psi.psi[end], digits=3))
# 4. Detect secular cycles
timeseries = 100.0 .+ 50.0 .* sin.(2π .* (1:300) ./ 100) .+ 2 .* randn(300)
analysis = secular_cycle_analysis(Float64.(timeseries), window=30)
println("Estimated cycle period: $(analysis.period) years")Model a complete secular cycle with coupled population, elite, and state dynamics:
using Cliodynamics
params = DemographicStructuralParams(
r=0.015, # Population growth rate (1.5% per year)
K=1000.0, # Carrying capacity
w=2.0, # Elite wage premium
δ=0.03, # Elite death/retirement rate
ε=0.001, # Elite production rate
N0=500.0, # Initial population (50% of capacity)
E0=10.0, # Initial elite population
S0=100.0 # Initial state fiscal capacity
)
sol = demographic_structural_model(params, tspan=(0.0, 300.0))
# Extract state at key timepoints
for t in [0.0, 100.0, 200.0, 300.0]
state = sol(t)
println("t=$t: N=$(round(state[1],digits=1)), E=$(round(state[2],digits=1)), S=$(round(state[3],digits=1))")
endAnalyze historical data from the Seshat Global History Databank:
using Cliodynamics
using DataFrames
# Load Seshat-format CSV data
raw = load_seshat_csv("data/seshat_sample.csv")
# Prepare Roman Empire data
roman = prepare_seshat_data(raw, polity="RomPrinworlds")
# Compute elite overproduction across Roman periods
roman_all = prepare_seshat_data(raw)
roman_all = filter(row -> occursin("Rom", string(row.polity)), roman_all)
sort!(roman_all, :year)
eoi = elite_overproduction_index(roman_all)
# Fit Malthusian model to English population growth
english = filter(row -> occursin("Eng", string(row.polity)), prepare_seshat_data(raw))
sort!(english, :year)
fit = fit_malthusian(Float64.(english.year), Float64.(english.population),
r_init=0.005, K_init=2_000_000.0)
println("Fitted r=$(round(fit.params.r, digits=6)), K=$(round(fit.params.K, digits=0))")using Cliodynamics
# Fit Malthusian model to observed data
years = collect(0.0:10.0:100.0)
population = [50.0 * exp(0.03 * t) / (1 + 50.0/500.0*(exp(0.03*t)-1)) for t in years]
result = fit_malthusian(years, population, r_init=0.01, K_init=600.0)
# Generic parameter estimation with bootstrap confidence intervals
model_fn(p, t) = p[1] .* exp.(p[2] .* (t .- t[1]))
est = estimate_parameters(model_fn, population, years, [50.0, 0.02], n_bootstrap=200)
println("r = $(round(est.params[2], digits=5)) 95% CI: [$(round(est.ci_lower[2], digits=5)), $(round(est.ci_upper[2], digits=5))]")Population grows until constrained by resources (carrying capacity K):
dN/dt = r*N*(1 - N/K)
When elite aspirants exceed available positions, intra-elite competition intensifies, destabilizing the political system:
EOI = (E/N) / (E/N)_baseline - 1
Composite index combining three destabilizing forces:
PSI = 0.4*MMP + 0.4*EMP + 0.2*SFD
where:
-
MMP = Mass Mobilization Potential (popular immiseration)
-
EMP = Elite Mobilization Potential (elite overproduction)
-
SFD = State Fiscal Distress (revenue crisis)
Long-term oscillations (150-300 years) with four phases:
-
Expansion: Low pressure, state strengthening, prosperity
-
Stagflation: Rising pressure, elite overproduction begins
-
Crisis: Political instability, state breakdown, conflict
-
Depression/Intercycle: Population decline, elite winnowing, recovery
-
Turchin, P. (2003). Historical Dynamics: Why States Rise and Fall. Princeton University Press.
-
Turchin, P. (2016). Ages of Discord: A Structural-Demographic Analysis of American History. Beresta Books.
-
Turchin, P., & Nefedov, S. A. (2009). Secular Cycles. Princeton University Press.
-
Turchin, P. (2023). End Times: Elites, Counter-Elites, and the Path of Political Disintegration. Penguin Press.
-
Goldstone, J. A. (1991). Revolution and Rebellion in the Early Modern World. University of California Press.
-
Korotayev, A., & Tsirel, S. (2010). "A Spectral Analysis of World GDP Dynamics." Structure and Dynamics, 4(1).
If you use this package in research, please cite:
@software{cliodynamics_jl,
author = {Jewell, Jonathan D.A.},
title = {Cliodynamics.jl: Mathematical Modeling of Historical Dynamics},
year = {2026},
url = {https://github.com/hyperpolymath/Cliodynamics.jl}
}And cite the foundational work:
@book{turchin2003historical,
author = {Turchin, Peter},
title = {Historical Dynamics: Why States Rise and Fall},
year = {2003},
publisher = {Princeton University Press}
}-
Seshat Global History Databank — Database for testing cliodynamic theories
-
Cliometrics.jl — Quantitative economic history
See CONTRIBUTING.md for guidelines.
Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
This project is licensed under the Palimpsest License (PMPL-1.0-or-later). See LICENSE for details.