The landscape below has several basins (valleys). Each dot is a candidate solution. On every step, each dot takes a Lévy-flight jump — usually short, occasionally long — and moves to the new position if it is lower. A fraction of the worst solutions are replaced (brood-parasitism step) with random new positions.
<!-- Layout: mode selector, step/run/reset buttons, canvas for landscape, status line -->
<div class="toolbar">
<label title="Choose jump distribution: Lévy (heavy-tailed, occasional long jumps) or Short (Gaussian-like small steps)">
Jump mode:
<select id="modeSelect">
<option value="levy">Lévy flight</option>
<option value="short">Short steps only</option>
</select>
</label>
<button id="stepBtn" type="button">Step</button>
<button id="runBtn" type="button">Run</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<canvas id="canvas" width="480" height="280" aria-label="Optimization landscape with candidate solutions shown as dots"></canvas>
<div id="status" class="status">Press Step or Run to start the search.</div>
/* Styles: toolbar, canvas, status. Canvas background is overdrawn by the heatmap. */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.toolbar { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .88rem; display: flex; align-items: center; gap: .35rem; }
select { font-size: .88rem; padding: .25rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 10px; background: #f7f9fb;
max-width: 100%; }
.status { font-size: .9rem; font-weight: 600; margin-top: .5rem; min-height: 1.4em; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cuckoo Search demo: Lévy-flight optimization on a synthetic multi-modal landscape.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// Synthetic landscape: sum of negative Gaussians = multiple basins of attraction.
// Basin 1 is shallow (local), basin 2 is the global minimum (deepest).
const BASINS = [
{ x: 0.18, y: 0.25, depth: 1.2, spread: 0.07 },
{ x: 0.50, y: 0.50, depth: 3.5, spread: 0.10 },
{ x: 0.78, y: 0.30, depth: 2.0, spread: 0.09 },
{ x: 0.35, y: 0.72, depth: 1.8, spread: 0.08 },
{ x: 0.65, y: 0.75, depth: 1.5, spread: 0.07 }
];
function fitness(nx, ny) {
// Lower is better; the global minimum is near basin index 1 (x≈0.50, y≈0.50).
let f = 0;
for (const b of BASINS) {
const dx = nx - b.x, dy = ny - b.y;
f -= b.depth * Math.exp(-(dx * dx + dy * dy) / (2 * b.spread * b.spread));
}
return f;
}
// Lévy sample using the Pareto inverse-CDF method: large values occur with probability proportional to u^(-1/alpha).
function levySample(alpha) {
// alpha=1.5 gives a moderately heavy tail (occasional very long steps).
const u = Math.random() + 1e-9;
return Math.pow(u, -1 / alpha);
}
const N_NESTS = 14;
const PA = 0.25; // fraction of worst nests replaced each generation (brood-parasitism rate)
let nests = [];
let generation = 0;
let running = false;
let runHandle = null;
function clamp(v) { return Math.max(0.01, Math.min(0.99, v)); }
function initNests() {
// Scatter nests uniformly at random across the unit square.
nests = Array.from({ length: N_NESTS }, () => {
const x = 0.05 + Math.random() * 0.9;
const y = 0.05 + Math.random() * 0.9;
return { x, y, f: fitness(x, y) };
});
generation = 0;
}
function step(mode) {
// One generation: each nest takes a Lévy (or short) step, then worst PA fraction is replaced.
for (let i = 0; i < nests.length; i++) {
let nx, ny;
if (mode === 'levy') {
// Lévy jump: scale by 0.04 to keep within canvas; direction is uniformly random.
const alpha = 1.5;
const len = 0.04 * levySample(alpha);
const angle = Math.random() * 2 * Math.PI;
nx = clamp(nests[i].x + len * Math.cos(angle));
ny = clamp(nests[i].y + len * Math.sin(angle));
} else {
// Short Gaussian-like step: ±0.025 in each axis — stays near current position.
const len = 0.025 * (Math.random() * 2 - 1);
const len2 = 0.025 * (Math.random() * 2 - 1);
nx = clamp(nests[i].x + len);
ny = clamp(nests[i].y + len2);
}
const nf = fitness(nx, ny);
if (nf < nests[i].f) { nests[i].x = nx; nests[i].y = ny; nests[i].f = nf; }
}
// Brood-parasitism replacement: discard the worst PA fraction and re-seed randomly.
nests.sort((a, b) => a.f - b.f);
const nReplace = Math.round(PA * nests.length);
for (let i = nests.length - nReplace; i < nests.length; i++) {
const x = 0.05 + Math.random() * 0.9;
const y = 0.05 + Math.random() * 0.9;
nests[i] = { x, y, f: fitness(x, y) };
}
generation++;
}
// Rendering helpers: heatmap of the landscape (built once) + nest dots.
let heatmap = null;
function buildHeatmap() {
// Build a pixel-level heatmap of fitness values; darker blue = deeper valley.
const imgData = ctx.createImageData(W, H);
let fMin = Infinity, fMax = -Infinity;
const vals = new Float32Array(W * H);
for (let py = 0; py < H; py++) {
for (let px = 0; px < W; px++) {
const f = fitness(px / W, py / H);
vals[py * W + px] = f;
if (f < fMin) fMin = f;
if (f > fMax) fMax = f;
}
}
const range = fMax - fMin || 1;
for (let i = 0; i < W * H; i++) {
const t = (vals[i] - fMin) / range; // t=0 at minimum (darkest), t=1 at maximum (lightest)
const r = Math.round(235 - t * 100);
const g = Math.round(245 - t * 60);
const b = Math.round(255 - t * 30);
imgData.data[i * 4] = r;
imgData.data[i * 4 + 1] = g;
imgData.data[i * 4 + 2] = b;
imgData.data[i * 4 + 3] = 255;
}
heatmap = imgData;
}
function draw() {
if (heatmap) ctx.putImageData(heatmap, 0, 0);
// Draw each nest as a filled circle; best nest is red and larger.
for (let i = 0; i < nests.length; i++) {
const px = nests[i].x * W, py = nests[i].y * H;
const isBest = i === 0;
ctx.beginPath();
ctx.arc(px, py, isBest ? 7 : 5, 0, 2 * Math.PI);
ctx.fillStyle = isBest ? '#e63946' : '#1d3557cc';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
ctx.stroke();
}
// Label the current best nest with a star.
const best = nests[0];
ctx.font = '11px system-ui';
ctx.fillStyle = '#e63946';
ctx.fillText('★', best.x * W - 5, best.y * H - 9);
}
function updateStatus() {
const best = nests[0];
const mode = document.getElementById('modeSelect').value;
const modeLabel = mode === 'levy' ? 'Lévy flight' : 'Short steps';
document.getElementById('status').textContent =
i18nFmt('Generation %gen% · %mode% · best fitness: %f%', { gen: generation, mode: modeLabel, f: best.f.toFixed(3) });
}
function doStep() {
const mode = document.getElementById('modeSelect').value;
step(mode);
nests.sort((a, b) => a.f - b.f);
draw();
updateStatus();
}
function reset() {
if (runHandle) { clearInterval(runHandle); runHandle = null; running = false; }
document.getElementById('runBtn').textContent = 'Run';
initNests();
buildHeatmap();
nests.sort((a, b) => a.f - b.f);
draw();
document.getElementById('status').textContent = 'Press Step or Run to start the search.';
}
document.getElementById('stepBtn').addEventListener('click', doStep);
document.getElementById('resetBtn').addEventListener('click', reset);
document.getElementById('runBtn').addEventListener('click', () => {
if (running) {
clearInterval(runHandle); runHandle = null; running = false;
document.getElementById('runBtn').textContent = 'Run';
} else {
running = true;
document.getElementById('runBtn').textContent = 'Pause';
runHandle = setInterval(doStep, 120);
}
});
reset();
<!-- Layout: mode selector, step/run/reset buttons, canvas for landscape, status line -->
<div class="toolbar">
<label title="Choose jump distribution: Lévy (heavy-tailed, occasional long jumps) or Short (Gaussian-like small steps)">
Jump mode:
<select id="modeSelect">
<option value="levy">Lévy flight</option>
<option value="short">Short steps only</option>
</select>
</label>
<button id="stepBtn" type="button">Step</button>
<button id="runBtn" type="button">Run</button>
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
<canvas id="canvas" width="480" height="280" aria-label="Optimization landscape with candidate solutions shown as dots"></canvas>
<div id="status" class="status">Press Step or Run to start the search.</div>
/* Styles: toolbar, canvas, status. Canvas background is overdrawn by the heatmap. */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.toolbar { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .88rem; display: flex; align-items: center; gap: .35rem; }
select { font-size: .88rem; padding: .25rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 10px; background: #f7f9fb;
max-width: 100%; }
.status { font-size: .9rem; font-weight: 600; margin-top: .5rem; min-height: 1.4em; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Cuckoo Search demo: Lévy-flight optimization on a synthetic multi-modal landscape.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// Synthetic landscape: sum of negative Gaussians = multiple basins of attraction.
// Basin 1 is shallow (local), basin 2 is the global minimum (deepest).
const BASINS = [
{ x: 0.18, y: 0.25, depth: 1.2, spread: 0.07 },
{ x: 0.50, y: 0.50, depth: 3.5, spread: 0.10 },
{ x: 0.78, y: 0.30, depth: 2.0, spread: 0.09 },
{ x: 0.35, y: 0.72, depth: 1.8, spread: 0.08 },
{ x: 0.65, y: 0.75, depth: 1.5, spread: 0.07 }
];
function fitness(nx, ny) {
// Lower is better; the global minimum is near basin index 1 (x≈0.50, y≈0.50).
let f = 0;
for (const b of BASINS) {
const dx = nx - b.x, dy = ny - b.y;
f -= b.depth * Math.exp(-(dx * dx + dy * dy) / (2 * b.spread * b.spread));
}
return f;
}
// Lévy sample using the Pareto inverse-CDF method: large values occur with probability proportional to u^(-1/alpha).
function levySample(alpha) {
// alpha=1.5 gives a moderately heavy tail (occasional very long steps).
const u = Math.random() + 1e-9;
return Math.pow(u, -1 / alpha);
}
const N_NESTS = 14;
const PA = 0.25; // fraction of worst nests replaced each generation (brood-parasitism rate)
let nests = [];
let generation = 0;
let running = false;
let runHandle = null;
function clamp(v) { return Math.max(0.01, Math.min(0.99, v)); }
function initNests() {
// Scatter nests uniformly at random across the unit square.
nests = Array.from({ length: N_NESTS }, () => {
const x = 0.05 + Math.random() * 0.9;
const y = 0.05 + Math.random() * 0.9;
return { x, y, f: fitness(x, y) };
});
generation = 0;
}
function step(mode) {
// One generation: each nest takes a Lévy (or short) step, then worst PA fraction is replaced.
for (let i = 0; i < nests.length; i++) {
let nx, ny;
if (mode === 'levy') {
// Lévy jump: scale by 0.04 to keep within canvas; direction is uniformly random.
const alpha = 1.5;
const len = 0.04 * levySample(alpha);
const angle = Math.random() * 2 * Math.PI;
nx = clamp(nests[i].x + len * Math.cos(angle));
ny = clamp(nests[i].y + len * Math.sin(angle));
} else {
// Short Gaussian-like step: ±0.025 in each axis — stays near current position.
const len = 0.025 * (Math.random() * 2 - 1);
const len2 = 0.025 * (Math.random() * 2 - 1);
nx = clamp(nests[i].x + len);
ny = clamp(nests[i].y + len2);
}
const nf = fitness(nx, ny);
if (nf < nests[i].f) { nests[i].x = nx; nests[i].y = ny; nests[i].f = nf; }
}
// Brood-parasitism replacement: discard the worst PA fraction and re-seed randomly.
nests.sort((a, b) => a.f - b.f);
const nReplace = Math.round(PA * nests.length);
for (let i = nests.length - nReplace; i < nests.length; i++) {
const x = 0.05 + Math.random() * 0.9;
const y = 0.05 + Math.random() * 0.9;
nests[i] = { x, y, f: fitness(x, y) };
}
generation++;
}
// Rendering helpers: heatmap of the landscape (built once) + nest dots.
let heatmap = null;
function buildHeatmap() {
// Build a pixel-level heatmap of fitness values; darker blue = deeper valley.
const imgData = ctx.createImageData(W, H);
let fMin = Infinity, fMax = -Infinity;
const vals = new Float32Array(W * H);
for (let py = 0; py < H; py++) {
for (let px = 0; px < W; px++) {
const f = fitness(px / W, py / H);
vals[py * W + px] = f;
if (f < fMin) fMin = f;
if (f > fMax) fMax = f;
}
}
const range = fMax - fMin || 1;
for (let i = 0; i < W * H; i++) {
const t = (vals[i] - fMin) / range; // t=0 at minimum (darkest), t=1 at maximum (lightest)
const r = Math.round(235 - t * 100);
const g = Math.round(245 - t * 60);
const b = Math.round(255 - t * 30);
imgData.data[i * 4] = r;
imgData.data[i * 4 + 1] = g;
imgData.data[i * 4 + 2] = b;
imgData.data[i * 4 + 3] = 255;
}
heatmap = imgData;
}
function draw() {
if (heatmap) ctx.putImageData(heatmap, 0, 0);
// Draw each nest as a filled circle; best nest is red and larger.
for (let i = 0; i < nests.length; i++) {
const px = nests[i].x * W, py = nests[i].y * H;
const isBest = i === 0;
ctx.beginPath();
ctx.arc(px, py, isBest ? 7 : 5, 0, 2 * Math.PI);
ctx.fillStyle = isBest ? '#e63946' : '#1d3557cc';
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
ctx.stroke();
}
// Label the current best nest with a star.
const best = nests[0];
ctx.font = '11px system-ui';
ctx.fillStyle = '#e63946';
ctx.fillText('★', best.x * W - 5, best.y * H - 9);
}
function updateStatus() {
const best = nests[0];
const mode = document.getElementById('modeSelect').value;
const modeLabel = mode === 'levy' ? 'Lévy flight' : 'Short steps';
document.getElementById('status').textContent =
i18nFmt('Generation %gen% · %mode% · best fitness: %f%', { gen: generation, mode: modeLabel, f: best.f.toFixed(3) });
}
function doStep() {
const mode = document.getElementById('modeSelect').value;
step(mode);
nests.sort((a, b) => a.f - b.f);
draw();
updateStatus();
}
function reset() {
if (runHandle) { clearInterval(runHandle); runHandle = null; running = false; }
document.getElementById('runBtn').textContent = 'Run';
initNests();
buildHeatmap();
nests.sort((a, b) => a.f - b.f);
draw();
document.getElementById('status').textContent = 'Press Step or Run to start the search.';
}
document.getElementById('stepBtn').addEventListener('click', doStep);
document.getElementById('resetBtn').addEventListener('click', reset);
document.getElementById('runBtn').addEventListener('click', () => {
if (running) {
clearInterval(runHandle); runHandle = null; running = false;
document.getElementById('runBtn').textContent = 'Run';
} else {
running = true;
document.getElementById('runBtn').textContent = 'Pause';
runHandle = setInterval(doStep, 120);
}
});
reset();
Comments
Loading comments...