The grid below is a 2-D lattice. Colored circles are atoms; gray squares are empty sites. Each atom can hop to any adjacent empty site. The hop rate depends on whether the destination is energetically favorable (downhill) or costly (uphill) — set the energy barrier with the slider and watch how quickly the atoms rearrange.
<!-- KMC demo – atoms hopping on a 2-D lattice -->
<div class="controls">
<label for="barrier">Energy barrier <span id="barrierVal">0.5</span> eV</label>
<input type="range" id="barrier" min="0.1" max="1.5" step="0.05" value="0.5">
<label for="temp">Temperature <span id="tempVal">600</span> K</label>
<input type="range" id="temp" min="200" max="1200" step="50" value="600">
</div>
<canvas id="kmc" width="300" height="300" title="KMC lattice simulation"></canvas>
<div class="info-row">
<span>Sim time: <b id="simTime">0.000</b> ns</span>
<span>Steps: <b id="stepCount">0</b></span>
</div>
<div class="status" id="status">Ready. Press Run or Step.</div>
<div class="btns">
<button id="btnRun" type="button">Run</button>
<button id="btnStep" type="button">Step</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem .9rem; align-items: center;
margin-bottom: .6rem; font-size: .88rem; }
.controls label { display: flex; align-items: center; gap: .35rem; }
input[type=range] { width: 110px; accent-color: #1d3557; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
width: 300px; height: 300px; }
.info-row { display: flex; gap: 1.4rem; font-size: .87rem; color: #444;
margin: .4rem 0 .2rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .2rem 0 .4rem; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kinetic Monte Carlo: pick next event proportional to its rate,
// advance simulated time by -ln(u)/R_total (exact for Markov processes),
// then fire the chosen event. Repeat.
const ROWS = 10, COLS = 10;
const KB = 8.617e-5; // Boltzmann constant in eV/K
const NU = 1e13; // attempt frequency (Hz), typical surface phonon
const COLORS = ['#e63946','#457b9d','#2a9d8f','#e9c46a','#f4a261'];
// grid[r][c] = 0 means empty, >0 = atom color index
let grid = []; // atom color index (0=empty)
let simTime = 0; // simulated time in nanoseconds
let steps = 0;
let running = false;
let rafId = null;
let barrier = 0.5;
let temp = 600;
function getBarrier() { return parseFloat(document.getElementById('barrier').value); }
function getTemp() { return parseFloat(document.getElementById('temp').value); }
// Arrhenius hop rate
function hopRate(Ea, T) { return NU * Math.exp(-Ea / (KB * T)); }
// 4-connected neighbors on the lattice
function neighbors(r, c) {
const dirs = [[-1,0],[1,0],[0,-1],[0,1]]; // up, down, left, right
return dirs.map(([dr, dc]) => [r + dr, c + dc])
.filter(([nr, nc]) => nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS);
}
// Collect all possible hops and their rates
function buildEvents() {
const Ea = getBarrier(), T = getTemp();
const events = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === 0) continue; // no atom here, skip
for (const [nr, nc] of neighbors(r, c)) {
if (grid[nr][nc] !== 0) continue; // destination is occupied, skip
// same barrier for all hops (symmetric model)
const rate = hopRate(Ea, T);
events.push({ r, c, nr, nc, rate });
}
}
}
return events;
}
// One KMC step – select and fire one event, advance time
function kmcStep() {
const events = buildEvents();
if (events.length === 0) return false;
const Rtotal = events.reduce((s, e) => s + e.rate, 0);
// draw a uniform random number for time advance
let u1 = Math.random();
while (u1 === 0) u1 = Math.random();
// residence time (exponential distribution)
const dt = -Math.log(u1) / Rtotal;
simTime += dt * 1e9; // convert seconds to nanoseconds
// pick event by roulette-wheel selection
let u2 = Math.random() * Rtotal, cum = 0, chosen = events[events.length - 1];
for (const ev of events) { cum += ev.rate; if (cum >= u2) { chosen = ev; break; } }
// move the atom to its new site
const id = grid[chosen.r][chosen.c];
grid[chosen.r][chosen.c] = 0;
grid[chosen.nr][chosen.nc] = id;
steps++;
return true;
}
const canvas = document.getElementById('kmc');
const ctx = canvas.getContext('2d');
const CW = canvas.width / COLS, CH = canvas.height / ROWS;
// Draw the lattice
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x = c * CW, y = r * CH;
if (grid[r][c] === 0) {
ctx.fillStyle = '#e8eef3';
ctx.fillRect(x + 1, y + 1, CW - 2, CH - 2);
} else {
ctx.fillStyle = COLORS[(grid[r][c] - 1) % COLORS.length];
ctx.beginPath();
ctx.arc(x + CW/2, y + CH/2, Math.min(CW, CH)/2 - 3, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
function updateUI() {
document.getElementById('simTime').textContent = simTime.toFixed(3);
document.getElementById('stepCount').textContent = steps;
const rate = hopRate(getBarrier(), getTemp());
document.getElementById('status').textContent =
i18nFmt('Hop rate: %rate% s⁻¹ per site', { rate: rate.toExponential(2) });
}
function initGrid() {
grid = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
// place 15 atoms at fixed starting positions
const atomPositions = [
[1,1],[1,4],[1,7],[2,2],[2,6],
[4,0],[4,3],[4,8],[5,5],[6,1],
[6,4],[6,7],[7,2],[8,3],[8,8]
];
atomPositions.forEach(([r, c], i) => { grid[r][c] = (i % COLORS.length) + 1; });
simTime = 0; steps = 0;
}
function reset() {
if (running) { running = false; cancelAnimationFrame(rafId); }
document.getElementById('btnRun').textContent = 'Run';
initGrid(); draw(); updateUI();
}
function runLoop() {
if (!running) return;
for (let i = 0; i < 5; i++) kmcStep(); // run 5 KMC steps per animation frame for visible speed
draw(); updateUI();
rafId = requestAnimationFrame(runLoop);
}
document.getElementById('btnRun').addEventListener('click', () => {
running = !running;
document.getElementById('btnRun').textContent = running ? 'Pause' : 'Run';
if (running) runLoop();
});
document.getElementById('btnStep').addEventListener('click', () => {
if (running) return;
kmcStep(); draw(); updateUI();
});
document.getElementById('btnReset').addEventListener('click', reset);
document.getElementById('barrier').addEventListener('input', (e) => {
document.getElementById('barrierVal').textContent = e.target.value;
updateUI();
});
document.getElementById('temp').addEventListener('input', (e) => {
document.getElementById('tempVal').textContent = e.target.value;
updateUI();
});
reset();
<!-- KMC demo – atoms hopping on a 2-D lattice -->
<div class="controls">
<label for="barrier">Energy barrier <span id="barrierVal">0.5</span> eV</label>
<input type="range" id="barrier" min="0.1" max="1.5" step="0.05" value="0.5">
<label for="temp">Temperature <span id="tempVal">600</span> K</label>
<input type="range" id="temp" min="200" max="1200" step="50" value="600">
</div>
<canvas id="kmc" width="300" height="300" title="KMC lattice simulation"></canvas>
<div class="info-row">
<span>Sim time: <b id="simTime">0.000</b> ns</span>
<span>Steps: <b id="stepCount">0</b></span>
</div>
<div class="status" id="status">Ready. Press Run or Step.</div>
<div class="btns">
<button id="btnRun" type="button">Run</button>
<button id="btnStep" type="button">Step</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem .9rem; align-items: center;
margin-bottom: .6rem; font-size: .88rem; }
.controls label { display: flex; align-items: center; gap: .35rem; }
input[type=range] { width: 110px; accent-color: #1d3557; }
canvas { display: block; border: 1px solid #cdd9e3; border-radius: 8px;
width: 300px; height: 300px; }
.info-row { display: flex; gap: 1.4rem; font-size: .87rem; color: #444;
margin: .4rem 0 .2rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .2rem 0 .4rem; color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Kinetic Monte Carlo: pick next event proportional to its rate,
// advance simulated time by -ln(u)/R_total (exact for Markov processes),
// then fire the chosen event. Repeat.
const ROWS = 10, COLS = 10;
const KB = 8.617e-5; // Boltzmann constant in eV/K
const NU = 1e13; // attempt frequency (Hz), typical surface phonon
const COLORS = ['#e63946','#457b9d','#2a9d8f','#e9c46a','#f4a261'];
// grid[r][c] = 0 means empty, >0 = atom color index
let grid = []; // atom color index (0=empty)
let simTime = 0; // simulated time in nanoseconds
let steps = 0;
let running = false;
let rafId = null;
let barrier = 0.5;
let temp = 600;
function getBarrier() { return parseFloat(document.getElementById('barrier').value); }
function getTemp() { return parseFloat(document.getElementById('temp').value); }
// Arrhenius hop rate
function hopRate(Ea, T) { return NU * Math.exp(-Ea / (KB * T)); }
// 4-connected neighbors on the lattice
function neighbors(r, c) {
const dirs = [[-1,0],[1,0],[0,-1],[0,1]]; // up, down, left, right
return dirs.map(([dr, dc]) => [r + dr, c + dc])
.filter(([nr, nc]) => nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS);
}
// Collect all possible hops and their rates
function buildEvents() {
const Ea = getBarrier(), T = getTemp();
const events = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === 0) continue; // no atom here, skip
for (const [nr, nc] of neighbors(r, c)) {
if (grid[nr][nc] !== 0) continue; // destination is occupied, skip
// same barrier for all hops (symmetric model)
const rate = hopRate(Ea, T);
events.push({ r, c, nr, nc, rate });
}
}
}
return events;
}
// One KMC step – select and fire one event, advance time
function kmcStep() {
const events = buildEvents();
if (events.length === 0) return false;
const Rtotal = events.reduce((s, e) => s + e.rate, 0);
// draw a uniform random number for time advance
let u1 = Math.random();
while (u1 === 0) u1 = Math.random();
// residence time (exponential distribution)
const dt = -Math.log(u1) / Rtotal;
simTime += dt * 1e9; // convert seconds to nanoseconds
// pick event by roulette-wheel selection
let u2 = Math.random() * Rtotal, cum = 0, chosen = events[events.length - 1];
for (const ev of events) { cum += ev.rate; if (cum >= u2) { chosen = ev; break; } }
// move the atom to its new site
const id = grid[chosen.r][chosen.c];
grid[chosen.r][chosen.c] = 0;
grid[chosen.nr][chosen.nc] = id;
steps++;
return true;
}
const canvas = document.getElementById('kmc');
const ctx = canvas.getContext('2d');
const CW = canvas.width / COLS, CH = canvas.height / ROWS;
// Draw the lattice
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x = c * CW, y = r * CH;
if (grid[r][c] === 0) {
ctx.fillStyle = '#e8eef3';
ctx.fillRect(x + 1, y + 1, CW - 2, CH - 2);
} else {
ctx.fillStyle = COLORS[(grid[r][c] - 1) % COLORS.length];
ctx.beginPath();
ctx.arc(x + CW/2, y + CH/2, Math.min(CW, CH)/2 - 3, 0, 2 * Math.PI);
ctx.fill();
}
}
}
}
function updateUI() {
document.getElementById('simTime').textContent = simTime.toFixed(3);
document.getElementById('stepCount').textContent = steps;
const rate = hopRate(getBarrier(), getTemp());
document.getElementById('status').textContent =
i18nFmt('Hop rate: %rate% s⁻¹ per site', { rate: rate.toExponential(2) });
}
function initGrid() {
grid = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
// place 15 atoms at fixed starting positions
const atomPositions = [
[1,1],[1,4],[1,7],[2,2],[2,6],
[4,0],[4,3],[4,8],[5,5],[6,1],
[6,4],[6,7],[7,2],[8,3],[8,8]
];
atomPositions.forEach(([r, c], i) => { grid[r][c] = (i % COLORS.length) + 1; });
simTime = 0; steps = 0;
}
function reset() {
if (running) { running = false; cancelAnimationFrame(rafId); }
document.getElementById('btnRun').textContent = 'Run';
initGrid(); draw(); updateUI();
}
function runLoop() {
if (!running) return;
for (let i = 0; i < 5; i++) kmcStep(); // run 5 KMC steps per animation frame for visible speed
draw(); updateUI();
rafId = requestAnimationFrame(runLoop);
}
document.getElementById('btnRun').addEventListener('click', () => {
running = !running;
document.getElementById('btnRun').textContent = running ? 'Pause' : 'Run';
if (running) runLoop();
});
document.getElementById('btnStep').addEventListener('click', () => {
if (running) return;
kmcStep(); draw(); updateUI();
});
document.getElementById('btnReset').addEventListener('click', reset);
document.getElementById('barrier').addEventListener('input', (e) => {
document.getElementById('barrierVal').textContent = e.target.value;
updateUI();
});
document.getElementById('temp').addEventListener('input', (e) => {
document.getElementById('tempVal').textContent = e.target.value;
updateUI();
});
reset();
At every KMC step the algorithm picks one event proportionally to its rate, fires it, and advances the simulation clock by Δt=−ln(u)/Rtotal where u is a uniform random number and Rtotal is the sum of all rates. Notice how the clock jumps in large leaps when rates are low (high barrier) and in tiny steps when rates are high (low barrier).
Comments
Loading comments...