# 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]].