UNPKG

cacatoo

Version:

Building, exploring, and sharing spatially structured models

430 lines (355 loc) 13.6 kB
<!-- /** * Chemotaxis toy model (temporal sensing): * * 1. Sense: * Each agent reads the local chemoattractant concentration R(x,y). * The receptor methylation M adapts slowly toward R. * M therefore acts as an internal expectation of “how good it was recently”. * * 2. Run: * If current concentration ≥ expectation (R ≥ M), * the agent continues straight (run), with slight angular noise. * * 3. Tumble: * If current concentration < expectation (R < M), * the agent enters a tumble phase: * - pure rotation * - no forward acceleration * After tumbling time, the agent resumes running in a new random direction. * * Biological intuition: * Cells compare “now” vs “recent past”. * This implements adaptation without explicit 2-component cascades. */ --> <html> <head> <title>Cacatoo examples</title> <script src="../../dist/cacatoo.js"></script> <!-- Cacatoo library --> <script src="../../lib/all.js"></script> <!-- Overige pakketten --> <link rel="stylesheet" href="../../style/cacatoo.css"> <!-- Stijlen --> </head> <script> /*----------------------- Start user-defined code ---------------------*/ let sim; // Globale parameters voor het model var num_startboids = 1000; let mycohesion = 0.0; let mycollision = 0.5; var resource_width = 150; var resource_noise_level = 0; var resource_noise_scale = 1; let nr_peaks = 1; let speed = 1; // Methylation- en tumble-parameters let methylation_increase = 0.1; // snelheid waarmee M richting R beweegt (run) let methylation_decay = 0.01; // afname van M tijdens tumble let avg_tumble_time = 30; // gemiddelde lengte van tumble-fase (in updates) // Voor polling van slider-veranderingen let last_resource_width = resource_width; let last_resource_noise_level = resource_noise_level; let last_resource_noise_scale = resource_noise_scale; let last_num_startboids = num_startboids; function cacatoo() { // Basisconfiguratie van de Cacatoo-simulatie const simconfig = { title: "Collective migration", description: "", maxtime: 1000000, ncol: 500, nrow: 500, scale: 1, sleep: 0, seed: 3, wrap: [false, false], graph_update: 10, graph_interval: 10, fpsmeter: false }; // Configuratie voor het boid-/bacteriemodel const flockconfig = { num_boids: 0, shape: 'rod', click: 'repel', max_speed: 2, max_force: 1, init_velocity: 0.0, friction: 0.01, brownian: 0.0, mouse_radius: 100, draw_mouse_radius: 'true', draw_mouse_colour: 'white', physics: false, collision_force: 2.0, size: 4.0, qt_capacity: 3 }; // Simulatie-object aanmaken sim = new Simulation(simconfig); // Modellen toevoegen: boids (flock) + grid (environment) sim.makeFlockmodel("flock", flockconfig); sim.makeGridmodel("environment"); // Beginwaarden voor chemo-attractant in de grid sim.initialGrid(sim.environment, "R", 0.1, 1); // Weergave van de veldconcentratie (log-schaal) sim.createDisplay_continuous({ model: "environment", property: "dispR", minval: -60.0, maxval: 30, num_colours: 100, decimals: 3, legend: true, legendlabel: "Concentration chemo-attractant", nticks: 1, fill: "inferno", label: "Bacterial chemotaxis" }); // Peakpositie: start in het midden sim.resource_x = sim.ncol / 2; sim.resource_y = sim.nrow / 2; // Plaats één "wolkige" resource-peak met ruis sim.placeResourceGradient = function (xpos, ypos, a, d, noise_radius, noise_strength) { let max_value_placed = -Infinity; let min_value_placed = +Infinity; const EPS = 1e-32; const step = Math.max(1, Math.floor(noise_radius)); // 1. Coarse noise veld const nx = Math.ceil(sim.ncol / step); const ny = Math.ceil(sim.nrow / step); const coarse = new Array(nx); for (let cx = 0; cx < nx; cx++) { coarse[cx] = []; for (let cy = 0; cy < ny; cy++) { coarse[cx][cy] = sim.rng.random(); // 0–1 } } // 2. Bilineaire interpolatie van het noise veld function sampleNoise(x, y) { const gx = x / step; const gy = y / step; const x0 = Math.floor(gx); const y0 = Math.floor(gy); const x1 = Math.min(x0 + 1, nx - 1); const y1 = Math.min(y0 + 1, ny - 1); const tx = gx - x0; const ty = gy - y0; const n00 = coarse[x0][y0]; const n10 = coarse[x1][y0]; const n01 = coarse[x0][y1]; const n11 = coarse[x1][y1]; const nx0 = n00 * (1 - tx) + n10 * tx; const nx1 = n01 * (1 - tx) + n11 * tx; return nx0 * (1 - ty) + nx1 * ty; } // 3. Veld opbouwen: Gaussische peak + gladde ruis for (let x = 0; x < sim.ncol; x++) { for (let y = 0; y < sim.nrow; y++) { // Gaussische peak rond (xpos, ypos) const dx = x - xpos; const dy = y - ypos; const distSq = dx * dx + dy * dy; const base = a * Math.exp(-(distSq / d)); // Noise in [-0.5..0.5] const n = sampleNoise(x, y) - 0.5; // Deformatie door noise let value = base * (1 + n * noise_strength); // Ondergrens voor stabiliteit (log-schaal en numeriek) if (value < EPS) value = EPS; sim.environment.grid[x][y].R += value; max_value_placed = Math.max(max_value_placed, value); min_value_placed = Math.min(min_value_placed, value); } } // Een beetje diffussie om het vlak smoother te maken for (let i = 0; i < 3; i++) { sim.environment.diffuseStates("R", 0.0001, 1.0, 10); } }; // Reset van resource-landschap (zelfde peakpositie, maar nieuwe breedte/ruis) sim.resetResource = function () { sim.initialGrid(sim.environment, "R", 0, 0, 1); const px = sim.resource_x; const py = sim.resource_y; sim.placeResourceGradient( px, py, 1, resource_width, resource_noise_scale, resource_noise_level ); }; // Verplaats de peak (nieuwe positie) en herbouw landschap met huidige sliderwaarden sim.movePeak = function () { sim.resource_x = 0.1 * sim.ncol + 0.8 * sim.ncol * sim.rng.random(); sim.resource_y = 0.1 * sim.nrow + 0.8 * sim.nrow * sim.rng.random(); sim.resetResource(); }; // Reset van bacterie-populatie (boids) sim.reset = function () { sim.flock.boids = []; sim.flock.populateSpot(num_startboids, sim.nr / 2, sim.nc / 2, 200); for (let boid of sim.flock.boids) { boid.cohesionstrength = mycohesion; boid.collision_force = mycollision; boid.fill = 'white'; boid.flagella = "directed"; boid.methylation = 1e-32; // klein maar > 0 boid.methylation_log = Math.log(boid.methylation); boid.tumble_time = 0; } }; sim.reset(); sim.resetResource(); // Weergave van bacteriën, ingekleurd op log(methylation) sim.createFlockDisplay("flock", { legend: true, addToDisplay: sim.canvases[0], property: "methylation_log", fill: 'rainbow', legendlabel: "Methylation level of bacterial receptors", strokeStyle: "white", strokeWidth: 2.5, minval: -12, maxval: -2, num_colours: 100, nticks: -1, decimals: 2 }); // Hoofd-update voor flock (run–tumble + methylation) /** * ------------------------------------------------------------------------- * TOY MODEL: BACTERIAL CHEMOTAXIS MET METHYLATION-GEHEUGEN * * 1. SENSE: * Lees lokale concentratie R. Methylation M beweegt langzaam richting R. * * 2. RUN: * Als omgeving ≥ geheugen (R ≥ M): bacterie blijft run uitvoeren * (vooruit + kleine ruis). * * 3. TUMBLE: * Als omgeving < geheugen (R < M): bacterie gaat een vaste tumble-fase in, * roteert willekeurig, en kiest daarna een nieuwe richting. * ------------------------------------------------------------------------- */ sim.flock.update = function () { sim.experienced_conc = 0; // Check of sliders zijn veranderd, zo ja: veld hergenereren, maar peakpositie gelijk if ( resource_width !== last_resource_width || resource_noise_level !== last_resource_noise_level || resource_noise_scale !== last_resource_noise_scale ) { // update cached slider values last_resource_width = resource_width; last_resource_noise_level = resource_noise_level; last_resource_noise_scale = resource_noise_scale; // 🧠 1. veld opnieuw opbouwen (peak blijft hetzelfde) sim.resetResource(); } if( num_startboids !== last_num_startboids){ last_num_startboids = num_startboids; sim.reset(); } for (let step = 0; step < speed; step++) { for (let boid of this.boids) { // 1. SENSE — lees lokale R const bx = Math.floor(boid.position.x); const by = Math.floor(boid.position.y); const R = sim.environment.grid[bx][by].R; sim.experienced_conc += R; // 2. METHYLATION — langzaam geheugen van R boid.methylation += methylation_increase * (R - boid.methylation); if (boid.methylation < 1e-32) boid.methylation = 1e-32; boid.methylation_log = Math.log(boid.methylation); const mag = Math.hypot(boid.velocity.x, boid.velocity.y) || 1; // 3. TUMBLE — rotate only, geen acceleratie if (boid.tumble_time > 0) { boid.flagella = "random"; boid.tumble_time--; const jitter = 0.3 + 0.3 * sim.rng.random(); const vx = boid.velocity.x; const vy = boid.velocity.y; const c = Math.cos(jitter); const s = Math.sin(jitter); boid.velocity.x = vx * c - vy * s; boid.velocity.y = vx * s + vy * c; // M daalt licht tijdens tumble (geheugen wordt "bescheidener") boid.methylation -= methylation_decay; if (boid.methylation < 1e-32) boid.methylation = 1e-32; continue; // sla run-logica over } else { boid.flagella = "directed"; boid.tumble_time = 0; } // 4. RUN — ga vooruit als R ≥ M, anders start tumble if (R >= boid.methylation) { boid.flagella = "directed"; // Richtingseenheid vector let ux = boid.velocity.x / mag; let uy = boid.velocity.y / mag; // Houd bacteriën weg van randen (reflectie-achtige duw) if (boid.position.x < 0.05 * sim.ncol) ux += 5.5; if (boid.position.x > 0.95 * sim.ncol) ux -= 5.5; if (boid.position.y < 0.05 * sim.nrow) uy += 5.5; if (boid.position.y > 0.95 * sim.nrow) uy -= 5.5; const acc = 0.03; boid.acceleration.x += ux * acc; boid.acceleration.y += uy * acc; // Kleine heading-ruis boid.velocity.x += (sim.rng.random() - 0.5) * 0.02; boid.velocity.y += (sim.rng.random() - 0.5) * 0.02; } else { // Omgeving slechter dan geheugen → tumble boid.tumble_time = avg_tumble_time + 5 * (2 * sim.rng.random() - 1); } } } }; // Grid-update: log-transform voor weergave sim.environment.nextState = function (x, y) { this.grid[x][y].dispR = Math.log(this.grid[x][y].R); }; sim.environment.update = function () { this.synchronous(); // Plot elke 20 stappen de totale chemo-attractant die door cellen ervaren wordt if (this.time % 20 === 0) { this.plotArray( ["[Chemoattractant]"], [sim.experienced_conc], ["orange"], "Chemoattractant concentration at cell locations", { height: 200 } ); } }; // UI-elementen en start simulatie sim.start(); sim.addButton("Start/pause", function () { sim.toggle_play(); }); sim.addButton("Single step", function () { sim.step(); }); sim.addButton("Reset bacteria", function () { sim.reset(); }); sim.addButton("Move peak", function () { sim.movePeak(); }); sim.addHTML("form_holder", "<br>"); sim.addSlider("resource_width", 1, 300, 0.1, "Resource width"); sim.addSlider("resource_noise_level", 0, 10, 1, "Resource noise (amount)"); sim.addSlider("resource_noise_scale", 0, 5, 1, "Resource noise (scale)"); sim.addSlider("num_startboids", 1, num_startboids*2, 1, "Nr of bacteria"); } </script> <body onload="cacatoo()" style="background-color: #e2e2e2;"> <div id="all_holder"> <table> <tr> <td> <div class="content" id="graph_holder"><img src="../../images/run_and_tumble.png" width="70%"></div> <div class="content" id="form_holder"></div> </td> <td><div class="content" id="canvas_holder"></div></td> </tr> </table> </div> </body> </html>