The canvas below shows a simple 2D search landscape. The hidden target (the optimum) is the darkest point. Each colored dot is a wolf — the pack starts scattered at random. Click Step to advance one iteration and watch alpha (gold), beta (silver) and delta (bronze) guide the rest of the pack as the encirclement tightens.
<!-- GWO demo: controls + canvas + status + legend -->
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
<span class="iter-label">Iteration: <strong id="iter-count">0</strong></span>
</div>
<canvas id="canvas" width="340" height="280" title="Grey Wolf Optimizer — wolf pack hunting the optimum"></canvas>
<div id="status" class="status"></div>
<div class="legend">
<span class="dot alpha-dot"></span>Alpha
<span class="dot beta-dot"></span>Beta
<span class="dot delta-dot"></span>Delta
<span class="dot omega-dot"></span>Omega
<span class="dot target-dot"></span>Target
</div>
/* Layout and color coding for the GWO demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.iter-label { font-size: .9rem; margin-left: .3rem; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px; background: #f4f7fa; cursor: default; }
.status { min-height: 1.5em; font-size: .95rem; font-weight: 600; margin: .4rem 0; }
.status.ok { color: #0a7d33; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .82rem; align-items: center; margin-top: .3rem; }
.dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 3px; }
.alpha-dot { background: #f5c518; border: 1.5px solid #b8930f; }
.beta-dot { background: #c0c0c0; border: 1.5px solid #888; }
.delta-dot { background: #cd7f32; border: 1.5px solid #8b5a1f; }
.omega-dot { background: #4a90d9; border: 1.5px solid #2c5f8a; }
.target-dot { background: #e63946; border: 1.5px solid #a02030; }
/* Grey Wolf Optimizer — 2D demo (vanilla JS, sandboxed) */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const W = 340, H = 280;
const N_WOLVES = 18; /* number of wolves in the pack */
const MAX_ITER = 60;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
/* objective function: squared distance to hidden target (lower is better) */
function objective(x, y) {
const dx = x - TARGET.x, dy = y - TARGET.y;
return dx * dx + dy * dy;
}
/* hidden target placed near the center with a random offset */
const TARGET = { x: 200 + Math.floor(Math.random() * 60 - 30),
y: 140 + Math.floor(Math.random() * 60 - 30) };
let wolves = [];
let alpha, beta, delta;
let iter = 0;
let running = false;
let runTimer = null;
/* scatter wolves randomly across the canvas and rank them */
function initPack() {
wolves = [];
for (let i = 0; i < N_WOLVES; i++) {
wolves.push({ x: 20 + Math.random() * (W - 40), y: 20 + Math.random() * (H - 40) });
}
rankWolves();
iter = 0;
document.getElementById('iter-count').textContent = iter;
const st = document.getElementById('status');
st.textContent = '';
st.className = 'status';
}
/* sort wolves by fitness and assign the top three as alpha, beta, delta */
function rankWolves() {
const sorted = wolves.slice().sort((a, b) => objective(a.x, a.y) - objective(b.x, b.y));
alpha = sorted[0];
beta = sorted[1];
delta = sorted[2];
}
/* advance one GWO iteration */
function stepGWO() {
if (iter >= MAX_ITER) return;
iter++;
document.getElementById('iter-count').textContent = iter;
/* linearly decreasing coefficient from 2 to 0 (exploration → exploitation) */
const a = 2 - 2 * (iter / MAX_ITER);
wolves = wolves.map(wolf => {
if (wolf === alpha || wolf === beta || wolf === delta) return wolf;
/* compute the pull from one leader toward this wolf */
function leaderPull(leader) {
const r1 = Math.random(), r2 = Math.random();
const A = 2 * a * r1 - a;
const C = 2 * r2;
const D = Math.abs(C * leader.x - wolf.x);
const Dy = Math.abs(C * leader.y - wolf.y);
return { x: leader.x - A * D, y: leader.y - A * Dy };
}
const p1 = leaderPull(alpha);
const p2 = leaderPull(beta);
const p3 = leaderPull(delta);
const nx = (p1.x + p2.x + p3.x) / 3;
const ny = (p1.y + p2.y + p3.y) / 3;
/* clamp new position to canvas bounds */
return { x: Math.max(5, Math.min(W - 5, nx)), y: Math.max(5, Math.min(H - 5, ny)) };
});
rankWolves();
const dist = Math.sqrt(objective(alpha.x, alpha.y));
if (dist < 12 || iter >= MAX_ITER) {
const st = document.getElementById('status');
st.textContent = i18nFmt('Pack converged in %iter% iteration(s) — target found!', { iter });
st.className = 'status ok';
stopRun();
}
draw();
}
/* render the landscape, target and all wolves */
function draw() {
ctx.clearRect(0, 0, W, H);
/* draw gradient background — lighter = better (closer to target) */
for (let px = 0; px < W; px += 4) {
for (let py = 0; py < H; py += 4) {
const v = objective(px, py) / ((W * W + H * H) / 4);
const light = Math.floor(220 - v * 80);
ctx.fillStyle = `rgb(${light},${light + 8},${Math.min(255, light + 20)})`;
ctx.fillRect(px, py, 4, 4);
}
}
/* draw the hidden target in red */
ctx.beginPath();
ctx.arc(TARGET.x, TARGET.y, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#e63946';
ctx.fill();
ctx.strokeStyle = '#a02030';
ctx.lineWidth = 1.5;
ctx.stroke();
/* draw each wolf; alpha/beta/delta are larger and distinctly colored */
wolves.forEach(w => {
const isAlpha = w === alpha, isBeta = w === beta, isDelta = w === delta;
const r = isAlpha ? 7 : isBeta ? 6 : isDelta ? 5 : 4;
ctx.beginPath();
ctx.arc(w.x, w.y, r, 0, 2 * Math.PI);
ctx.fillStyle = isAlpha ? '#f5c518' : isBeta ? '#c0c0c0' : isDelta ? '#cd7f32' : '#4a90d9';
ctx.fill();
ctx.strokeStyle = isAlpha ? '#b8930f' : isBeta ? '#888' : isDelta ? '#8b5a1f' : '#2c5f8a';
ctx.lineWidth = 1.2;
ctx.stroke();
});
}
function stopRun() {
running = false;
clearInterval(runTimer);
document.getElementById('btn-run').textContent = 'Run';
}
document.getElementById('btn-step').addEventListener('click', () => {
stopRun();
stepGWO();
});
document.getElementById('btn-run').addEventListener('click', () => {
if (running) {
stopRun();
} else {
if (iter >= MAX_ITER) return;
running = true;
document.getElementById('btn-run').textContent = 'Pause';
runTimer = setInterval(() => { stepGWO(); if (iter >= MAX_ITER) stopRun(); }, 120);
}
});
document.getElementById('btn-reset').addEventListener('click', () => {
stopRun();
initPack();
draw();
});
initPack();
draw();
<!-- GWO demo: controls + canvas + status + legend -->
<div class="controls">
<button id="btn-step" type="button">Step</button>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
<span class="iter-label">Iteration: <strong id="iter-count">0</strong></span>
</div>
<canvas id="canvas" width="340" height="280" title="Grey Wolf Optimizer — wolf pack hunting the optimum"></canvas>
<div id="status" class="status"></div>
<div class="legend">
<span class="dot alpha-dot"></span>Alpha
<span class="dot beta-dot"></span>Beta
<span class="dot delta-dot"></span>Delta
<span class="dot omega-dot"></span>Omega
<span class="dot target-dot"></span>Target
</div>
/* Layout and color coding for the GWO demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.iter-label { font-size: .9rem; margin-left: .3rem; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px; background: #f4f7fa; cursor: default; }
.status { min-height: 1.5em; font-size: .95rem; font-weight: 600; margin: .4rem 0; }
.status.ok { color: #0a7d33; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .82rem; align-items: center; margin-top: .3rem; }
.dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 3px; }
.alpha-dot { background: #f5c518; border: 1.5px solid #b8930f; }
.beta-dot { background: #c0c0c0; border: 1.5px solid #888; }
.delta-dot { background: #cd7f32; border: 1.5px solid #8b5a1f; }
.omega-dot { background: #4a90d9; border: 1.5px solid #2c5f8a; }
.target-dot { background: #e63946; border: 1.5px solid #a02030; }
/* Grey Wolf Optimizer — 2D demo (vanilla JS, sandboxed) */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const W = 340, H = 280;
const N_WOLVES = 18; /* number of wolves in the pack */
const MAX_ITER = 60;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
/* objective function: squared distance to hidden target (lower is better) */
function objective(x, y) {
const dx = x - TARGET.x, dy = y - TARGET.y;
return dx * dx + dy * dy;
}
/* hidden target placed near the center with a random offset */
const TARGET = { x: 200 + Math.floor(Math.random() * 60 - 30),
y: 140 + Math.floor(Math.random() * 60 - 30) };
let wolves = [];
let alpha, beta, delta;
let iter = 0;
let running = false;
let runTimer = null;
/* scatter wolves randomly across the canvas and rank them */
function initPack() {
wolves = [];
for (let i = 0; i < N_WOLVES; i++) {
wolves.push({ x: 20 + Math.random() * (W - 40), y: 20 + Math.random() * (H - 40) });
}
rankWolves();
iter = 0;
document.getElementById('iter-count').textContent = iter;
const st = document.getElementById('status');
st.textContent = '';
st.className = 'status';
}
/* sort wolves by fitness and assign the top three as alpha, beta, delta */
function rankWolves() {
const sorted = wolves.slice().sort((a, b) => objective(a.x, a.y) - objective(b.x, b.y));
alpha = sorted[0];
beta = sorted[1];
delta = sorted[2];
}
/* advance one GWO iteration */
function stepGWO() {
if (iter >= MAX_ITER) return;
iter++;
document.getElementById('iter-count').textContent = iter;
/* linearly decreasing coefficient from 2 to 0 (exploration → exploitation) */
const a = 2 - 2 * (iter / MAX_ITER);
wolves = wolves.map(wolf => {
if (wolf === alpha || wolf === beta || wolf === delta) return wolf;
/* compute the pull from one leader toward this wolf */
function leaderPull(leader) {
const r1 = Math.random(), r2 = Math.random();
const A = 2 * a * r1 - a;
const C = 2 * r2;
const D = Math.abs(C * leader.x - wolf.x);
const Dy = Math.abs(C * leader.y - wolf.y);
return { x: leader.x - A * D, y: leader.y - A * Dy };
}
const p1 = leaderPull(alpha);
const p2 = leaderPull(beta);
const p3 = leaderPull(delta);
const nx = (p1.x + p2.x + p3.x) / 3;
const ny = (p1.y + p2.y + p3.y) / 3;
/* clamp new position to canvas bounds */
return { x: Math.max(5, Math.min(W - 5, nx)), y: Math.max(5, Math.min(H - 5, ny)) };
});
rankWolves();
const dist = Math.sqrt(objective(alpha.x, alpha.y));
if (dist < 12 || iter >= MAX_ITER) {
const st = document.getElementById('status');
st.textContent = i18nFmt('Pack converged in %iter% iteration(s) — target found!', { iter });
st.className = 'status ok';
stopRun();
}
draw();
}
/* render the landscape, target and all wolves */
function draw() {
ctx.clearRect(0, 0, W, H);
/* draw gradient background — lighter = better (closer to target) */
for (let px = 0; px < W; px += 4) {
for (let py = 0; py < H; py += 4) {
const v = objective(px, py) / ((W * W + H * H) / 4);
const light = Math.floor(220 - v * 80);
ctx.fillStyle = `rgb(${light},${light + 8},${Math.min(255, light + 20)})`;
ctx.fillRect(px, py, 4, 4);
}
}
/* draw the hidden target in red */
ctx.beginPath();
ctx.arc(TARGET.x, TARGET.y, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#e63946';
ctx.fill();
ctx.strokeStyle = '#a02030';
ctx.lineWidth = 1.5;
ctx.stroke();
/* draw each wolf; alpha/beta/delta are larger and distinctly colored */
wolves.forEach(w => {
const isAlpha = w === alpha, isBeta = w === beta, isDelta = w === delta;
const r = isAlpha ? 7 : isBeta ? 6 : isDelta ? 5 : 4;
ctx.beginPath();
ctx.arc(w.x, w.y, r, 0, 2 * Math.PI);
ctx.fillStyle = isAlpha ? '#f5c518' : isBeta ? '#c0c0c0' : isDelta ? '#cd7f32' : '#4a90d9';
ctx.fill();
ctx.strokeStyle = isAlpha ? '#b8930f' : isBeta ? '#888' : isDelta ? '#8b5a1f' : '#2c5f8a';
ctx.lineWidth = 1.2;
ctx.stroke();
});
}
function stopRun() {
running = false;
clearInterval(runTimer);
document.getElementById('btn-run').textContent = 'Run';
}
document.getElementById('btn-step').addEventListener('click', () => {
stopRun();
stepGWO();
});
document.getElementById('btn-run').addEventListener('click', () => {
if (running) {
stopRun();
} else {
if (iter >= MAX_ITER) return;
running = true;
document.getElementById('btn-run').textContent = 'Pause';
runTimer = setInterval(() => { stepGWO(); if (iter >= MAX_ITER) stopRun(); }, 120);
}
});
document.getElementById('btn-reset').addEventListener('click', () => {
stopRun();
initPack();
draw();
});
initPack();
draw();
Notice how the pack quickly identifies a rough neighborhood of the target and then refines. The three leaders pull the whole pack toward their best-known positions, and the shrinking coefficient a (which decreases linearly from 2 to 0) makes the pack transition automatically from exploration (wide, random movement) to exploitation (tight circling of the current best).
Comments
Loading comments...