# System dynamics
System dynamics is the modeling methodology that represents complex systems as [[Stock_and_flow|stocks, flows]], [[Feedback|feedback loops]], delays, and nonlinear table functions, then simulates their behavior over time. Originating with [[Jay_Wright_Forrester|Forrester]], it uses [[Causal_loop_diagram|causal-loop]] and stock-flow diagrams to explore policy leverage, exponential growth, oscillations, and limits-to-growth scenarios.
<!-- LEGACYSIM:BEGIN v1.5 — generated by g03_mint_wave.py; three.js first; do not hand-edit inside -->
## Microsims (promoted from legacy — three.js first)
### Microsim
### Live player
<div class="microsim-player">
<iframe src="https://editor.p5js.org/sciencenibber/full/VG2lh_GDS" width="100%" height="620" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
<div class="microsim-fallback">
<img src="Microsims/thumbs/System_dynamics.png" alt="System_dynamics microsim poster" style="width:100%;border:1px solid #4445;border-radius:6px;">
<p><em>Live microsim (desktop) · <a href="https://editor.p5js.org/sciencenibber/sketches/VG2lh_GDS">open sketch in the p5.js editor</a></em></p>
</div>
**Editor URL:** https://editor.p5js.org/sciencenibber/sketches/VG2lh_GDS
**Description (100 words):**
The sketch animates a single stock fed by an inflow and drained by an outflow, the canonical Forrester structure. A yellow tank fills with the stock level S(t); an orange arrow streams tokens into the tank while a blue arrow drains them. Three sliders set the exogenous inflow I, the outflow rate constant k, and a reinforcing-feedback gain g that couples outflow back into inflow. A scrolling scope plots S(t), inflow(t), and outflow(t) together, with a dashed equilibrium line at I divided by (k minus g). The bottom label names the regime — first-order tank, goal-seeking with reinforcing loop, neutral, or runaway exponential when g exceeds k.
```js
// =====================================================================
// System_dynamics.js -- Wikitube microsim
// Article: System_dynamics en.wikitube.io/wiki/System_dynamics
// Room: Helium Pattern: K (stock-and-flow,
// conservation, balance
// equations)
// ---------------------------------------------------------------------
// Idea: the canonical Forrester sketch -- one Stock fed by an Inflow
// and drained by an Outflow, with an optional reinforcing-feedback
// coupling that turns the structure from a balancing first-order
// system into a runaway one. The reader scrubs three sliders and
// watches the live trajectory of S(t) reveal the central lesson of
// system dynamics: *structure determines behavior*.
//
// Conservation law (the only equation that matters here):
//
// dS/dt = inflow(t) - outflow(t)
//
// In this sketch:
//
// inflow(t) = I + g * S (g >= 0: reinforcing loop)
// outflow(t) = k * S (k > 0: balancing loop)
//
// So the integrated ODE is
//
// dS/dt = (I + g * S) - k * S = I + (g - k) * S
//
// Three regimes the reader can drive into existence:
//
// * g = 0 pure first-order tank, S -> I/k, time const tau = 1/k.
// * g < k still goal-seeking, S -> I/(k - g).
// * g = k neutral; S grows linearly at rate I.
// * g > k runaway exponential -- the "limits to growth"
// archetype without the limit.
//
// Integration uses forward-Euler with dt clamped to deltaTime/1000,
// capped at 0.05 s so a paused tab can't blow the stock to Infinity
// on resume. The stock is clamped to [0, S_MAX] so the tank stays on
// screen even in the runaway regime -- a "limits to growth" implicit
// ceiling provided by the rendering geometry rather than by physics.
//
// Visual layout (720 x 520 canvas):
// * top-left: HUD title + en.wikitube.io/wiki/System_dynamics subtitle
// * top-right: ASCII control hints (drag sliders, press R to reset)
// * center-left: stock tank (vertical bar that fills with level)
// * around tank: inflow arrow on the left, outflow arrow on the right,
// with animated tokens whose density encodes flow rate
// * center-right: scrolling time-series scope of S(t), I(t), k*S(t)
// * bottom: three sliders (I, k, g) + reset button + readouts
// * bottom-right: canonical equation in ASCII
//
// Conventions (Wikitube Betterfire Standard v0):
// * single ARTICLE constant at the top, single quotes
// * p5.disableFriendlyErrors = true to keep the editor console clean
// * non-ASCII (Greek tau, sigma, arrows, en-dashes) lives in COMMENTS
// ONLY; every text() string literal is ASCII (the editor preview
// pipeline mangles non-ASCII in strings)
// * Energy-room palette (P5_JS_EDITOR section 4 line 165)
// * sliders all positioned + sized explicitly (BF requirement)
// =====================================================================
const ARTICLE = 'System_dynamics';
const TITLE = ARTICLE.replace(/_/g, ' ');
p5.disableFriendlyErrors = true;
// ----- Energy room palette (P5_JS_EDITOR section 4, line 165) --------
const BG = 18;
const FG = 240;
const DIM = [240, 240, 240, 140];
const HOT = [220, 110, 60]; // warm: inflow (source)
const COLD = [60, 130, 220]; // cool: outflow (sink)
const STRUCT = [120, 130, 150]; // structural grey: tank walls, axes
const TRAJ = [240, 220, 80]; // accent: stock level + S(t) curve
const REIN = [200, 100, 220]; // magenta: reinforcing feedback loop
const SCRATCH = [120, 120, 120, 90]; // grid / scratch lines
// ----- Model state ---------------------------------------------------
// S = the single stock (units: arbitrary "widgets" or "kg-He")
// t = simulation time (seconds)
// I = exogenous inflow rate (slider)
// k = outflow rate constant per unit stock (slider)
// g = reinforcing-feedback gain on inflow (slider)
const S_MAX = 100.0; // visual ceiling -- the tank's top edge
const BUF_LEN = 480; // ring-buffer length for the scope (frames)
let S = 10.0; // initial stock
let t = 0.0; // simulation time
let buf = []; // [{ t, S, inflow, outflow }, ...]
// ----- DOM controls (positions set in setup) ------------------------
let iSlider, kSlider, gSlider, resetBtn;
// ----- Token-flow animation phase -----------------------------------
// Tokens are dots drifting along the inflow and outflow arrows. Their
// per-frame stride is proportional to the *actual* flow rate, so a
// large flow visibly streams while a near-zero flow visibly stalls.
let inflowPhase = 0;
let outflowPhase = 0;
function setup() {
createCanvas(720, 520);
pixelDensity(2);
textFont('system-ui');
// Slider layout band along the bottom. Three sliders + a reset
// button. Y-coords are absolute pixels relative to canvas top-left.
const sy = 442;
iSlider = createSlider(0, 10, 2.0, 0.1 ).position(20, sy ).size(180);
kSlider = createSlider(0.02, 1.0, 0.20, 0.01).position(20, sy + 24).size(180);
gSlider = createSlider(0, 1.0, 0.0, 0.01).position(20, sy + 48).size(180);
resetBtn = createButton('reset').position(210, sy + 48);
resetBtn.mousePressed(resetState);
}
function resetState() {
S = 10.0;
t = 0.0;
buf = [];
inflowPhase = 0;
outflowPhase = 0;
}
function keyPressed() {
// 'r' / 'R' as a keyboard shortcut for reset (BF nice-to-have).
if (key === 'r' || key === 'R') resetState();
}
// =====================================================================
// Integration step + draw loop
// =====================================================================
function draw() {
background(BG);
// ---- read controls once into named locals (readable physics) ----
const I = iSlider.value();
const k = kSlider.value();
const g = gSlider.value();
const dt = Math.min(deltaTime / 1000, 0.05);
// ---- forward-Euler step of dS/dt = (I + g*S) - k*S --------------
const inflowRate = I + g * S;
const outflowRate = k * S;
S += (inflowRate - outflowRate) * dt;
// Clamp the stock so the tank stays on screen and the integrator
// does not run away in the g > k regime. The clamp is part of the
// pedagogy -- "limits to growth" is rendered by the geometry.
S = constrain(S, 0, S_MAX);
t += dt;
// Ring buffer for the scope
buf.push({ t: t, S: S, inflow: inflowRate, outflow: outflowRate });
if (buf.length > BUF_LEN) buf.shift();
// Advance token-flow animations in proportion to actual flow rate.
inflowPhase = (inflowPhase + inflowRate * dt * 30) % 24;
outflowPhase = (outflowPhase + outflowRate * dt * 30) % 24;
// ---- rendering ---------------------------------------------------
drawTank(I, k, g);
drawScope(buf, k);
drawSliderLabels(I, k, g);
drawHUD();
}
// =====================================================================
// Stock-and-flow visualization (Pattern K idiom)
// =====================================================================
// Tank rectangle and arrow geometry.
const TANK_X = 100;
const TANK_Y = 90;
const TANK_W = 110;
const TANK_H = 280;
function drawTank(I, k, g) {
push();
// Outline of the tank
noFill();
stroke(...STRUCT);
strokeWeight(2);
rect(TANK_X, TANK_Y, TANK_W, TANK_H, 4);
// Liquid level inside the tank: fill from the bottom up to S/S_MAX.
const levelFrac = S / S_MAX;
const liquidH = TANK_H * levelFrac;
noStroke();
fill(TRAJ[0], TRAJ[1], TRAJ[2], 220);
rect(TANK_X + 1, TANK_Y + TANK_H - liquidH, TANK_W - 2, liquidH - 1);
// Tank labels
fill(...DIM);
textSize(11);
textAlign(CENTER, BOTTOM);
text('Stock S', TANK_X + TANK_W / 2, TANK_Y - 4);
textAlign(CENTER, TOP);
text(nf(S, 0, 2), TANK_X + TANK_W / 2, TANK_Y + TANK_H + 4);
// ---- Inflow arrow (left of tank, pointing right into the top) ---
drawFlowArrow(TANK_X - 70, TANK_Y + 20, TANK_X - 2, TANK_Y + 20,
HOT, inflowPhase, 'inflow I + g*S');
// ---- Outflow arrow (right of tank, pointing right out) ----------
drawFlowArrow(TANK_X + TANK_W + 2, TANK_Y + TANK_H - 20,
TANK_X + TANK_W + 70, TANK_Y + TANK_H - 20,
COLD, outflowPhase, 'outflow k*S');
// ---- Reinforcing-feedback arc (looping outflow path back into ---
// the inflow when g > 0). Drawn dashed in REIN colour so the
// reader sees the structural difference between g = 0 and g > 0.
if (g > 0.005) {
push();
stroke(REIN[0], REIN[1], REIN[2], 200);
strokeWeight(2);
noFill();
// Arc from the bottom of the tank, around, up to the inflow
bezier(TANK_X + TANK_W / 2, TANK_Y + TANK_H + 18,
TANK_X + TANK_W / 2, TANK_Y + TANK_H + 70,
TANK_X - 110, TANK_Y + TANK_H + 70,
TANK_X - 70, TANK_Y + 26);
// Arrowhead at the inflow end
fill(REIN[0], REIN[1], REIN[2], 220);
noStroke();
triangle(TANK_X - 70, TANK_Y + 26,
TANK_X - 76, TANK_Y + 22,
TANK_X - 76, TANK_Y + 32);
// Label
fill(...REIN);
textSize(10);
textAlign(CENTER, TOP);
text('reinforcing loop (g)', TANK_X - 30, TANK_Y + TANK_H + 50);
pop();
}
pop();
}
// drawFlowArrow -- a single horizontal arrow with animated tokens.
// (x0,y0) -> (x1,y1) must have x1 > x0; tokens drift left -> right.
function drawFlowArrow(x0, y0, x1, y1, col, phase, label) {
push();
// Shaft
stroke(col[0], col[1], col[2], 220);
strokeWeight(3);
line(x0, y0, x1, y1);
// Arrowhead
noStroke();
fill(col[0], col[1], col[2], 220);
triangle(x1, y1, x1 - 8, y1 - 5, x1 - 8, y1 + 5);
// Animated tokens: small dots spaced every 24 px, offset by phase
fill(col[0], col[1], col[2], 255);
noStroke();
const len = x1 - x0;
for (let px = x0 + phase; px < x1 - 4; px += 24) {
circle(px, y0, 4);
}
// Label
fill(col[0], col[1], col[2], 230);
textSize(10);
textAlign(CENTER, BOTTOM);
text(label, (x0 + x1) / 2, y0 - 8);
pop();
}
// =====================================================================
// Scope: scrolling time-series of S(t), inflow(t), and outflow(t)
// =====================================================================
const SCOPE_X = 260;
const SCOPE_Y = 80;
const SCOPE_W = 430;
const SCOPE_H = 320;
function drawScope(samples, k) {
push();
// Scope frame
noFill();
stroke(SCRATCH);
strokeWeight(1);
rect(SCOPE_X, SCOPE_Y, SCOPE_W, SCOPE_H);
// Gridlines: 4 horizontal divisions of S, vertical at one-quarter steps
for (let i = 1; i < 4; i++) {
const y = SCOPE_Y + (SCOPE_H * i) / 4;
stroke(SCRATCH);
line(SCOPE_X, y, SCOPE_X + SCOPE_W, y);
}
for (let i = 1; i < 4; i++) {
const x = SCOPE_X + (SCOPE_W * i) / 4;
stroke(SCRATCH);
line(x, SCOPE_Y, x, SCOPE_Y + SCOPE_H);
}
// Axis labels
noStroke();
fill(...DIM);
textSize(10);
textAlign(RIGHT, CENTER);
text(nf(S_MAX, 0, 0), SCOPE_X - 4, SCOPE_Y);
text('0', SCOPE_X - 4, SCOPE_Y + SCOPE_H);
textAlign(LEFT, TOP);
text('S(t)', SCOPE_X + 6, SCOPE_Y + 4);
textAlign(RIGHT, BOTTOM);
text('time (s) ->', SCOPE_X + SCOPE_W - 6, SCOPE_Y + SCOPE_H - 4);
// Equilibrium line: if g < k, the stock heads toward I/(k-g).
const I = iSlider.value();
const g = gSlider.value();
if (k - g > 0.001) {
const S_eq = I / (k - g);
if (S_eq > 0 && S_eq < S_MAX) {
const y = map(S_eq, 0, S_MAX, SCOPE_Y + SCOPE_H, SCOPE_Y);
stroke(STRUCT[0], STRUCT[1], STRUCT[2], 180);
strokeWeight(1);
drawingContext.setLineDash([4, 4]);
line(SCOPE_X, y, SCOPE_X + SCOPE_W, y);
drawingContext.setLineDash([]);
noStroke();
fill(...DIM);
textSize(10);
textAlign(LEFT, BOTTOM);
text('S_eq = ' + nf(S_eq, 0, 2), SCOPE_X + 6, y - 2);
}
}
// Plot S(t) over a sliding window of the most recent samples.
if (samples.length >= 2) {
const n = samples.length;
const tMin = samples[0].t;
const tMax = samples[n - 1].t;
const tSpan = Math.max(tMax - tMin, 1e-3);
// S(t) -- yellow trajectory line
noFill();
stroke(...TRAJ);
strokeWeight(2);
beginShape();
for (const s of samples) {
const sx = map(s.t, tMin, tMax, SCOPE_X, SCOPE_X + SCOPE_W);
const sy = map(s.S, 0, S_MAX, SCOPE_Y + SCOPE_H, SCOPE_Y);
vertex(sx, sy);
}
endShape();
// inflow(t) and outflow(t) -- secondary curves on a separate scale.
// Plot them in the bottom 60% of the scope so they read as
// "rates" rather than "levels". The scale is chosen against the
// worst case at the current slider settings.
const rateScale = Math.max(I + g * S_MAX, k * S_MAX, 1.0);
stroke(HOT[0], HOT[1], HOT[2], 200);
strokeWeight(1.5);
beginShape();
for (const s of samples) {
const sx = map(s.t, tMin, tMax, SCOPE_X, SCOPE_X + SCOPE_W);
const sy = map(s.inflow, 0, rateScale, SCOPE_Y + SCOPE_H, SCOPE_Y + SCOPE_H * 0.4);
vertex(sx, sy);
}
endShape();
stroke(COLD[0], COLD[1], COLD[2], 200);
strokeWeight(1.5);
beginShape();
for (const s of samples) {
const sx = map(s.t, tMin, tMax, SCOPE_X, SCOPE_X + SCOPE_W);
const sy = map(s.outflow, 0, rateScale, SCOPE_Y + SCOPE_H, SCOPE_Y + SCOPE_H * 0.4);
vertex(sx, sy);
}
endShape();
}
// Legend
noStroke();
textSize(10);
textAlign(LEFT, TOP);
fill(...TRAJ); text('S(t) stock', SCOPE_X + 6, SCOPE_Y + 18);
fill(...HOT); text('inflow(t) I + g*S', SCOPE_X + 6, SCOPE_Y + 32);
fill(...COLD); text('outflow(t) k*S', SCOPE_X + 6, SCOPE_Y + 46);
pop();
}
// =====================================================================
// Slider labels (rendered on canvas alongside the DOM sliders)
// =====================================================================
function drawSliderLabels(I, k, g) {
push();
noStroke();
fill(...DIM);
textSize(11);
textAlign(LEFT, CENTER);
text('I (exogenous inflow) = ' + nf(I, 0, 2), 210, 449);
text('k (outflow rate const) = ' + nf(k, 0, 2) + ' /s', 210, 473);
// g label appears next to its slider, the reset button is to its right
textAlign(LEFT, CENTER);
fill(g > 0.005 ? REIN : DIM);
text('g (reinforcing gain) = ' + nf(g, 0, 2) + ' /s', 260, 497);
pop();
}
// =====================================================================
// HUD: title, URL, control hints, equation
// =====================================================================
function drawHUD() {
// Read current control values from the DOM sliders so the HUD can be
// called from draw() with zero arguments -- the Betterfire validator
// requires the literal call `drawHUD()` somewhere in the source.
const I = iSlider.value();
const k = kSlider.value();
const g = gSlider.value();
push();
// Top-left: title + Wikitube URL (BF rule 2)
noStroke();
fill(FG);
textAlign(LEFT, TOP);
textSize(20);
text(TITLE, 14, 12);
fill(...DIM);
textSize(12);
text('Wikitube microsim . en.wikitube.io/wiki/System_dynamics', 14, 36);
// Top-right: control hints (BF rule 3)
textAlign(RIGHT, TOP);
textSize(10);
fill(...DIM);
text('drag sliders to set I, k, g', width - 14, 12);
text('press R to reset the stock', width - 14, 24);
text('g > k drives runaway exponential', width - 14, 36);
// Bottom-right: canonical equation (BF rule 4)
textAlign(RIGHT, BOTTOM);
fill(FG);
textSize(13);
text('dS/dt = (I + g*S) - k*S', width - 14, height - 6);
// Bottom-left: regime label so the lesson is explicit
const regime =
(g > k + 0.005) ? 'regime: runaway (g > k, reinforcing dominates)' :
(Math.abs(g - k) <= 0.005) ? 'regime: neutral (g = k, linear growth at rate I)' :
(g > 0.005) ? 'regime: goal-seeking with reinforcing loop' :
'regime: first-order tank (balancing only)';
fill(...DIM);
textAlign(LEFT, BOTTOM);
textSize(11);
text(regime, 14, height - 6);
pop();
}
// =====================================================================
// End of System_dynamics.js -- Wikitube microsim, Helium room, Pattern K.
// =====================================================================
```
### MicroSim spec
### Parameters (tunable controls)
- `Inflow rate` · 0–10 · rate filling the stock per unit time
- `Outflow fraction` · 0–1 · fraction of the stock draining each step
- `Delay` · 0–20 · time lag in the balancing feedback loop
### What animates
A stock rectangle fills and drains as inflow and outflow tokens move along arrows.
### Learning objective
Show how stocks, flows, and delays produce nonlinear behaviour over time.
### MicroSim spec
- **Recommended sim type:** stock-and-flow diagram
- **Microsimmability score:** 98/100
- **Layout:** stock reservoir with inflow/outflow arrows on the canvas; sliders below.
### Parameters (tunable controls)
- `Inflow rate`
- `Outflow rate`
- `Initial stock`
### What animates
A stock fills and drains as inflow and outflow rates change; the level and net flow update live toward equilibrium.
### Learning objective
Relate inflow/outflow balance to whether a stock grows, depletes, or holds steady.
### MicroSim spec
- **Recommended sim type:** stock & flow
- **Microsimmability score:** 90/100
- **Layout:** drawing region (canvas) on top; control region (sliders/buttons) below.
### Parameters (tunable controls)
- `Inflow rate`
- `Outflow rate`
- `Feedback gain`
### What animates
Stocks fill and drain through feedback-linked flows, oscillating or settling as gain changes.
### Learning objective
Show how stocks, flows, and feedback together drive a [[System|system]]'s behavior over time.
### MicroSim spec
- **Recommended sim type:** stock-and-flow
- **Microsimmability score:** 88/100
- **Layout:** drawing region (canvas) on top; control region (sliders/buttons) below.
### Parameters (tunable controls)
- `Inflow rate`
- `Outflow rate`
- `Feedback gain`
### What animates
Stocks and flows linked by feedback loops evolve over time, showing growth, decline, or overshoot.
### Learning objective
Model a [[System|system]]'s behavior over time using stocks, flows, and feedback loops.
<!-- LEGACYSIM:END -->
## Reveal
%%REVEAL:p5%%
%%REVEAL:mermaid%%
---
*Concept aligned with [Wikipedia](https://en.wikipedia.org/wiki/System_dynamics); adapted text, where present, is licensed [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).*
## Overview
Under the diagrams sits a [[Nonlinear_system|nonlinear]] mathematics: reinforcing [[Positive_feedback|positive]] and balancing [[Negative_feedback|negative feedback]] coupled through delays, capable of [[Chaos_theory|chaotic]] and oscillatory regimes familiar from [[Cybernetics|cybernetics]] and the wider [[Systems_theory|systems-theory]] tradition of [[Ludwig_von_Bertalanffy|von Bertalanffy]] and [[Norbert_Wiener|Wiener]]. The method is the executable arm of [[Systems_thinking|systems thinking]] within [[Systems_science|systems science]].
In practice it complements [[Operations_research|operations research]] and [[Mathematical_optimization|optimization]] for policy design, borrows validation discipline from [[Systems_engineering|systems engineering]] and [[Reliability_engineering|reliability engineering]], models an [[Ecosystem|ecosystem]] or an economy with equal ease through [[Population_dynamics|population dynamics]], and scales from a single [[System|system]] model to institution-wide simulation in the spirit of [[Stafford_Beer|Stafford Beer]].
<!-- CRAFT-LINK:START g12 -->
*Built to the [[WT!P5_js_Microsim_Master_Class|p5.js Master Class]].*
<!-- CRAFT-LINK:END -->
## Wikipedia : Wikitube
**Strict pair:** [Wikipedia](https://en.wikipedia.org/wiki/System_dynamics) : [Wikitube](https://en.wikitube.io/wiki/System_dynamics)
## Previous hub tags
Hubs: `Systems`. Portals: [[PORTAL_Systems]], [[PORTAL_System_dynamics]].