An open API service indexing awesome lists of open source software.

https://github.com/hinanohart/dostosim

Dostoevskian agent-based simulation: seven literary archetypes as a unified reward decomposition. Anti-optimisation, dignity-as-utility, ideological cascade, gambler-ruin — pre-registered H1-H4 with bootstrap CI / BH-FDR / Cliff's delta.
https://github.com/hinanohart/dostosim

abm agent-based-modeling behavioral-economics bootstrap-ci computational-social-science dostoevsky literature networkx numpy python reproducible-research

Last synced: about 2 months ago
JSON representation

Dostoevskian agent-based simulation: seven literary archetypes as a unified reward decomposition. Anti-optimisation, dignity-as-utility, ideological cascade, gambler-ruin — pre-registered H1-H4 with bootstrap CI / BH-FDR / Cliff's delta.

Awesome Lists containing this project

README

          

# dostosim

> *"I am a sick man... I am a spiteful man. I am an unattractive man. I believe my liver is diseased. However, I know nothing at all about my disease."*
> — Dostoevsky, *Notes from Underground*

**dostosim** is a Python agent-based simulation library that formalises seven literary archetypes from Fyodor Dostoevsky's corpus — together with Sartre's "freedom is hell" and Akerlof–Kranton identity economics — as **a single additive reward decomposition over a Watts–Strogatz network**. It is, deliberately, a model of *the failure modes of utility maximisation*: the agents do not converge to a homo-economicus equilibrium, and the library's purpose is to characterise the systematic ways in which they fail to.

The model is a methodological continuation of the conflict-punisher project (Fearon-1995 bargaining + stochastic punisher). Where that project asked *"when does deterrence stabilise an interaction?"*, dostosim asks the dual: *"when does an agent rationally refuse to be stabilised?"*

[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
[![Type-checked: mypy strict](https://img.shields.io/badge/typed-mypy%20strict-blue.svg)](http://mypy-lang.org/)

---

## The seven archetypes

| Archetype | Source | What it formalises | α | β | γ | δ | ε | ζ |
|---|---|---|---|---|---|---|---|---|
| `UNDERGROUND_MAN` | *Notes from Underground* | The "2 + 2 = 5" rebellion: a positive cost on *being seen to optimise* | 0.5 | 0.3 | 0.1 | 0 | 0 | **1.2** |
| `GAMBLER` | *The Gambler* | Stimulus reward overpowers monetary reward; means become ends | 0.2 | 0.1 | **1.0** | 0 | 0.1 | 0 |
| `RASKOLNIKOV` | *Crime and Punishment* | "Extraordinary man" doctrine + delayed self-punishment | 1.0 | 0.4 | 0 | 0.3 | **0.9** | 0 |
| `IDIOT_MYSHKIN` | *The Idiot* | Universal benevolence as constitutive; positive externality on neighbours | 0.3 | **1.2** | 0 | 0.2 | 0.1 | 0 |
| `DEMON_HOST` | *Demons* (*Бесы*) | Ideology-as-parasite: agents become tools of a memetic payload | 0.4 | 0.2 | 0.2 | **1.5** | 0 | 0 |
| `POOR_FOLK_MAKAR` | *Poor Folk* | The button scene: dignity dominates material reward | 0.6 | **1.0** | 0 | 0 | 0.2 | 0 |
| `NASTASYA` | *The Idiot* (heroine) | Self-destructive identity preservation; *negative* α and β | **−0.5** | **−0.8** | 0.3 | 0 | 0.7 | 0 |

Coefficients are not fitted — they are chosen so that single-archetype runs reproduce the literary character's qualitative behaviour. The *quantitative* parameters live in [`src/dostosim/core/archetypes.py`](src/dostosim/core/archetypes.py) and are sweep-able via the ablation script.

## The unified reward function

For each agent *i* deciding action *a* against neighbour *j*, the utility is

```
U_i(a, j) = α_i · m(a) # material change
+ β_i · d(a) # dignity / recognition gain
+ γ_i · s(a) # stimulus (|Δwealth|)
+ δ_i · 𝟙[a ⊨ ι_i] # ideology alignment
− ε_i · p_i(a) # accumulated moral debt
− ζ_i · Û*_i(a, j) # anti-optimisation, the Underground term
```

The anti-optimisation term is the conceptual core: it *subtracts* the expected utility of the action under the standard maximiser, so a sufficiently large ζ flips the agent into actively rejecting whichever option a textbook agent would pick. This is the formal statement of the *Notes from Underground* narrator's claim that *"a man would rather suffer than be a piano-key in the laws of arithmetic."*

The Û*_i term uses one-step lookahead with ζ zeroed out, so the operator is a contraction (no infinite regress).

## Pre-registered hypotheses

Each is tested with bootstrap-CI / BH-FDR / Cliff's δ — see [`src/dostosim/analysis/`](src/dostosim/analysis/).

| ID | Statement | Metric | Direction |
|---|---|---|---|
| **H1** | Underground agents form a structurally isolated sub-community | clustering z-score of UNDERGROUND_MAN vs rest | z < −2 |
| **H2** | A single DEMON-ideology seed triggers a super-linear contagion | demon-fraction(T), R₀ from logistic fit | fraction > 0.30, R₀ > 1 |
| **H3** | An IDIOT_MYSHKIN agent doubles dignity-variance in its neighbourhood | ratio of neighbourhood var to non-neighbour var | ratio > 2 |
| **H4** | Gamblers go bankrupt ∝ γ², independent of expected wealth | gambler bankruptcy / non-gambler bankruptcy | ratio > 5 |

We will report **PASS / FAIL with effect sizes regardless of direction**, in the spirit of the conflict-punisher project's H9-FAIL precedent. A negative result is data, not failure.

## Quick start

```bash
pip install -e ".[dev]"
pytest # 49 tests, ~8 s
python scripts/run_ablation.py --seeds 100 --steps 2000 \
--agents 200 --workers 6 --out results/ablation.json
```

For the smallest possible smoke run (~1 s):

```python
from dostosim import Simulation, SimulationConfig
result = Simulation(SimulationConfig(n_agents=50, n_steps=200, seed=42)).run()
print(result.wealth().mean(), result.action_counts)
```

## Reproducibility

* All simulations are seeded through a single `np.random.Generator`.
* Bit-exact reproducibility is asserted in [`tests/test_simulate.py`](tests/test_simulate.py).
* Bootstrap CIs are BCa with `n_resamples=10_000`; multiple-comparison correction is BH-FDR.

## Why bother formalising literature?

Because the alternative is leaving a vast empirical record of how human beings *actually* behave — written by the writer who saw further into deviation from rational choice than any 19th-century economist — outside the model class entirely. Dostoevsky's archetypes are not metaphor: they are sharp behavioural predictions about

* what people will do *because* it is irrational (Underground),
* what stops feeling like an instrument once it has become an end (Gambler),
* how one act of moral exception calls forth its own punishment (Raskolnikov),
* why pure altruism destabilises strangers more than it does friends (Myshkin),
* how an idea *uses* the person who claims to hold it (Demons),
* the way a hundred-rouble note can wound or rescue depending on the *gesture* (Poor Folk),
* and why having a chance to be happy is, for some people, intolerable (Nastasya).

Each of these has direct analogues in modern behavioural economics (Akerlof–Kranton identity, Bénabou–Tirole self-signalling, Loewenstein visceral influences, Camerer & Thaler ultimatum bargaining). dostosim's contribution is to put them in *one* coefficient space so that ablation answers *which one* drives any given emergent phenomenon.

## Results — H1-H4 verdicts (100 seeds × 2000 steps × 200 agents)

Reported with bootstrap-CI / BH-FDR, full pre-registration in [`docs/HYPOTHESES.md`](docs/HYPOTHESES.md).

| Hypothesis | metric | mean | 95% CI | threshold | outcome |
|---|---|---|---|---|---|
| H1 | Underground isolation (clustering z) | +0.127 | [+0.066, +0.186] | < −2.0 | **FAIL** |
| H2 | Demon-cascade final fraction | 0.167 | [0.165, 0.170] | > 0.30 | **FAIL** |
| H3 | Idiot dignity-variance ratio | 0.891 | [0.853, 0.926] | > 2.0 | **FAIL** |
| H4 | Gambler bankruptcy ratio | 6.65 | [5.83, 8.08] | > 5.0 | **PASS** |

**Three failures are not the same as a vacuous result**, and each one localises the model's disagreement with the source text:

* **H1 (Underground)** — the anti-optimisation term *does* shift action distribution toward ABSTAIN (verified in `scripts/run_ablation.py --conditions zeta_zero`: removing ζ raises wealth by 35%), but ABSTAIN does not perturb local clustering on a Watts–Strogatz graph because triangles are formed by *other* pairs. The Underground man is socially detached in his head; he is not detached in the network. *This is itself a Dostoevskian point*, but our metric did not capture it.
* **H2 (Demons)** — at the calibrated susceptibility (0.05) and Idiot prevalence (10%), one seed cannot punch through to 30% market share within T=2000. Raising susceptibility to 0.20 (`high_susceptibility` condition) only reaches 14.7% saturation. The Idiot's `IDIOT` ideology actively blocks `DEMON` flips, an emergent immunity not foreseen at design time.
* **H3 (Idiot)** — and here the model contradicts the literary claim outright. Myshkin's neighbourhoods have **lower** dignity variance (0.89×, with CI strictly below 1), not higher. This aligns with Dawes & Thaler (1988) *prosocial saturation reduces variance* and is the opposite of what *The Idiot* suggests. Two readings are possible: either dostosim's broadcast-externality formalisation is too uniform to capture Myshkin's selective destabilising effect on *jealous self-conscious* agents, or Dostoevsky generalised from individual case to population in a way the formal model rejects.
* **H4 (Gambler)** — passes cleanly: Gamblers are 6.65× more likely to go bankrupt than the rest, with a CI well clear of the threshold. Means become ends; expected wealth is constant; bankruptcy probability is large.

The `all_idiots` ablation is the most striking single number: a 200-agent population of pure-Myshkin agents reaches mean wealth **1468** (vs baseline 608), 100% alive, 0% Demon penetration. Universal benevolence wins, but only when there is no one to be jealous of.

### Argmax vs soft-max policy: which verdicts survive?

A natural follow-up to v0.2's soft-max is to re-run the *same* H1-H4 ablation under τ=0.5 and ask which verdicts are robust against policy specification.

| Hypothesis | argmax (τ=0) | soft-max (τ=0.5) | interpretation |
|---|---|---|---|
| H1 Underground isolation | FAIL z = +0.13 | FAIL (NaN — Underground sub-population too thin) | Argmax-only artefact: under softmax, Underground agents die via SELF_DESTRUCT before they can form a structural cluster |
| H2 Demon cascade | FAIL frac = 0.17 | FAIL frac = 0.03 | Cascade weakens further under softmax exploration; the proselytize advantage requires deterministic susceptibility |
| H3 Idiot destabilisation | FAIL ratio = 0.89 (reversed) | FAIL (NaN) | Idiot-Myshkin no longer broadcasts to a stable neighbourhood |
| **H4 Gambler bankruptcy** | **PASS ratio = 6.65** | **FAIL ratio = 1.66** | The most striking flip: argmax kept non-Gamblers from gambling at all, so all bankruptcy was Gambler-driven; under softmax everyone gambles occasionally |

**Population health collapses under softmax**: alive-fraction drops from 87 % (argmax baseline) to 15 % (τ=0.5 baseline), and the `all_idiots` paradise from 1468 → 15.5 mean wealth. Random exploration is incompatible with the cooperative equilibria that argmax discovers.

This is itself the v0.2.1 finding: **only H4 was a clear PASS at all, and it does not survive the policy-specification robustness check.** All four pre-registered hypotheses are now best read as falsified relative to the bedrock literary predictions, with the model nonetheless producing rich emergent dynamics that *match* the literary narratives in unexpected places (γ-sweep self-destruct exit, all_idiots paradise, Idiot stabilisation alignment with Dawes-Thaler).

See [`figures/softmax/`](figures/softmax/) for the parallel forest plots and [`results/ablation_tau05.json`](results/ablation_tau05.json) for the raw 100-seed × 2000-step × 200-agent × 8-condition table.

### H4 τ-sweep — a *non-monotonic* phase structure

`scripts/tau_sweep.py` characterises the bankruptcy-ratio collapse at fine resolution (`τ ∈ {0, 0.05, 0.10, 0.20, 0.50, 1.00, 2.00}`, 30 seeds × 2000 steps × 200 agents):

| τ | rate-ratio mean | 95 % CI |
|---|---|---|
| 0.00 | **5.83** | [4.94, 6.80] |
| 0.05 | **0.57** | [0.31, 1.05] |
| 0.10 | **0.50** | [0.29, 0.79] |
| 0.20 | 0.83 | [0.56, 1.17] |
| 0.50 | 1.69 | [1.40, 2.07] |
| 1.00 | 1.54 | [1.26, 1.91] |
| 2.00 | 1.26 | [1.07, 1.49] |

The transition is **not monotonic**. Three regimes:

1. **τ = 0 (argmax)** — ratio = 5.83, PASS. Non-Gamblers never gamble, so all bankruptcy is Gambler-driven.
2. **0 < τ ≤ 0.2 (argmax-perturbed)** — ratio drops *below 1*: Gamblers fare **better** than non-Gamblers. Mechanism: Gamblers reach the SELF_DESTRUCT threshold first (wealth = 0, which is *not* bankruptcy), while non-Gamblers bleed below zero through occasional random gambles they never asked for.
3. **τ ≥ 0.5 (full exploration)** — ratio plateaus near 1.5; no archetype-specific signal.

The "verdict" registered by the script is `UNEXPECTED PROFILE` because the pre-registered direction (monotonic decay) is wrong. This is the most striking single finding of the project: **the H4 PASS does not just disappear under exploration — it inverts**.

See [`figures/h4_tau_sweep.png`](figures/h4_tau_sweep.png).

A **fine-grid sub-sweep** (`scripts/tau_sweep.py --grid fine`) brackets the cliff edge:

| τ | ratio | 95 % CI |
|---|---|---|
| 0.000 | **5.83** | [4.96, 6.82] |
| 0.005 | 0.67 | [0.53, 0.85] |
| 0.010 | **0.095** | [0.031, 0.223] |
| 0.020 | 0.18 | [0.07, 0.36] |
| 0.030 | 0.46 | [0.24, 0.79] |
| 0.050 | 0.57 | [0.31, 1.06] |

The transition is essentially a step function: any τ > 0 collapses the ratio by an order of magnitude. The deepest inversion is at **τ ≈ 0.01**, where Gamblers are ten times *less* likely to bankrupt than non-Gamblers (CI strictly below 0.25). See [`figures/h4_tau_sweep_fine.png`](figures/h4_tau_sweep_fine.png) and [`docs/FINDINGS.md`](docs/FINDINGS.md) for the full paper-style synthesis.

### H2 cascade time-course

[`figures/cascade_timecourse.png`](figures/cascade_timecourse.png) plots the demon-fraction trajectory φ(t) under three regimes (baseline, high_susceptibility=0.20, no_demon control) with 95 % bootstrap-CI ribbons across 30 seeds. Even at quadrupled susceptibility the trajectory plateaus near 0.30 only briefly and never crosses the H2 threshold — the cascade is sub-critical at this network size.

### Per-archetype action distribution

The empirical validation that the unified six-dimensional reward decomposition really does recover seven distinct literary characters is the single most interpretable figure in the project — see [`figures/action_heatmap.png`](figures/action_heatmap.png). Each row shows the action frequency of one archetype across 3 seeds × 500 steps; the seven rows are visibly different, and each one matches the source character (Underground ABSTAINS, Gambler GAMBLES, Demon-host PROSELYTIZES, Myshkin COOPERATES, Nastasya alternates DEFECT and SELF_DESTRUCT, etc.).

### Identifiability of the six coefficients

`scripts/identifiability.py` constructs the empirical Fisher information matrix `F = JᵀJ` from finite-difference Jacobians around the canonical UNDERGROUND_MAN, IDIOT_MYSHKIN, and DEMON_HOST coefficient vectors. The rank of `F` counts how many coefficient directions are *separately identifiable* from the chosen 11-dimensional summary vector.

**Argmax policy (default, v0.1 reproducible):**

| archetype | rank @ h = 0.05 | rank @ h = 0.30 | condition number @ h = 0.30 |
|---|---|---|---|
| UNDERGROUND_MAN | 1 / 6 | 5 / 6 | 4.4 × 10¹¹ |
| IDIOT_MYSHKIN | 1 / 6 | 1 / 6 | ∞ |
| DEMON_HOST | 1 / 6 | 2 / 6 | 9.8 |

**Soft-max policy at τ = 0.5 (added in v0.2):**

| archetype | rank @ h = 0.05 | condition number |
|---|---|---|
| UNDERGROUND_MAN | **6 / 6** | 8.1 × 10¹¹ |
| IDIOT_MYSHKIN | **6 / 6** | 2.2 × 10⁶ |
| DEMON_HOST | **6 / 6** | 1.5 × 10⁵ |

The deterministic argmax policy makes the *local* Fisher rank piecewise zero — perturbations smaller than the gap to the second-best action change nothing. v0.2's softmax temperature recovers smooth gradients in every direction and restores full rank at all three reference archetypes, formally answering the Analyst's separability concern. See [`docs/IDENTIFIABILITY.md`](docs/IDENTIFIABILITY.md).

### H4 follow-up — γ-sweep (`scripts/gamma_sweep.py`, 50 seeds × 2000 steps × 200 agents × 7 γ levels)

H4's textbook derivation predicts bankruptcy rate ∝ γ². The empirical fit is the **opposite**:

| γ | bankruptcy rate (95 % CI) | dominant exit mode |
|---|---|---|
| 0.00 | 0.000 | no gambling, no exit |
| 0.25 | 0.165 [0.152, 0.177] | bankruptcy (wealth < 0) |
| 0.50 | 0.165 [0.152, 0.177] | bankruptcy |
| 0.75 | 0.155 [0.143, 0.167] | bankruptcy |
| 1.00 | 0.161 [0.149, 0.173] | bankruptcy |
| **1.25** | **0.005 [0.002, 0.009]** | **SELF_DESTRUCT** |
| **1.50** | **0.005 [0.002, 0.009]** | **SELF_DESTRUCT** |

Log-log slope **k = −1.96, 95 % CI [−2.36, −1.75]** — a **negative** quadratic. At high γ the dominant Gambler exit flips from "wealth slowly bleeds below zero" to "self-destruct at the height of obsession", because gambling shame (`+0.02` per gamble to `self_punish_drive`) crosses the SELF_DESTRUCT threshold *before* the random walk reaches zero. SELF_DESTRUCT zeroes wealth without sending it negative, so the bankruptcy counter stops registering.

This is, almost word-for-word, the *Gambler*: the protagonist does not gradually bleed his fortune at the table; he wagers it at the *climax* of obsession and discharges it in a single act. The model produces this transition without it being designed in. See [`figures/h4_gamma_sweep.png`](figures/h4_gamma_sweep.png).

Figures: [`figures/h1_forest.png`](figures/h1_forest.png) … [`figures/h4_forest.png`](figures/h4_forest.png), [`figures/wealth_violin.png`](figures/wealth_violin.png).

## Methodological notes

* **Identification.** Because all archetypes share the same six-dimensional reward vector, ablation is a linear sweep, not a re-implementation. This is deliberate — heterogeneous reward functions per agent type make the model unfalsifiable.
* **Network.** Watts–Strogatz with k=6, p=0.1, n=200 gives clustering needed for H1 and short paths needed for H2.
* **Sample size.** 100 seeds per condition × 8 conditions = 800 runs; total wall time ≈ 12 min on 6 CPU cores.
* **Sanity check.** With all six coefficients set to zero, every action returns zero utility — the model degenerates to uniform random tie-break, which the test suite asserts.

## Project status

Status: **v0.3.1 (research-grade research preview)** as of 2026-04-27. Seven GitHub releases (v0.1.0 → v0.3.1) document the iterations:

| version | one-line summary |
|---|---|
| v0.1.0 | seven archetypes × 6-dim reward, H1-H4 ablation (3 FAIL 1 PASS), γ-sweep, identifiability, action heatmap |
| v0.2.0 | soft-max policy; Fisher rank 1/6 → 6/6 |
| v0.2.1 | argmax-vs-softmax verdict comparison; H4 PASS does not survive |
| v0.2.2 | τ-sweep reveals non-monotonic phase structure |
| v0.3.0 | fine-grid τ-sweep — cliff is a step function; FINDINGS.md capstone |
| v0.3.1 | per-archetype exit-mode flip (Gambler 0% → 78% SELF_DESTRUCT in τ=0.01) |

57 passing pytest cases, ruff + mypy strict, GitHub Actions CI on Python 3.10/3.11/3.12. PyPI build artefacts are in `dist/` (`twine check` PASS); see [`PUBLISHING.md`](PUBLISHING.md) for the upload procedure. The full paper-style synthesis lives at [`docs/FINDINGS.md`](docs/FINDINGS.md).

## Citing

```bibtex
@software{dostosim2026,
author = {dostosim contributors},
title = {dostosim: Dostoevskian agent-based simulation},
year = {2026},
url = {https://github.com/hinanohart/dostosim},
version = {0.1.0}
}
```

## Related work

* **conflict-punisher** — methodological predecessor; same bootstrap/ablation protocol.
* **Mesa** — general ABM toolkit; we use plain `numpy` + `networkx` for ~5× speed and zero plug-in surface.
* **Akerlof, G., & Kranton, R. (2010)** *Identity Economics.* Princeton UP. — closest formal antecedent of the β term.
* **Frank, R. (1985)** *Choosing the Right Pond.* Oxford UP. — status-as-utility microfoundations.
* **Bénabou, R., & Tirole, J. (2006)** *Incentives and Prosocial Behavior.* AER 96(5). — for the ε self-signalling term.
* **Watts, D., & Strogatz, S. (1998)** *Collective dynamics of "small-world" networks.* Nature 393. — the network model.
* **Efron, B., & Tibshirani, R. (1993)** *An Introduction to the Bootstrap.* Chapman & Hall. — for BCa CIs.

## License

MIT. See [LICENSE](LICENSE).