The demo below runs a simplified CEGAR loop on a tiny program. The abstract model starts with no predicates — it treats all states as equivalent. Press Step to advance the loop: the checker finds a path to the error, the simulator checks whether it is real or spurious, and if spurious it picks the right predicate to add and refines.
<!-- CEGAR demo: abstract model on left, loop state on right -->
<div class="cegar-wrap">
<div class="prog-box">
<div class="box-title">Program (pseudocode)</div>
<pre class="prog-code" id="prog-code"></pre>
</div>
<div class="loop-box">
<div class="box-title">CEGAR Loop State</div>
<div id="phase-label" class="phase-label"></div>
<div id="pred-list" class="pred-list"></div>
<div id="cex-box" class="cex-box hidden"></div>
<div id="result-box" class="result-box hidden"></div>
</div>
</div>
<div class="btn-row">
<button id="btn-step" type="button">Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="log" class="log"></div>
/* Styles for the CEGAR loop visualizer */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.cegar-wrap { display: flex; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.prog-box, .loop-box { flex: 1 1 180px; border: 1px solid #cdd9e3; border-radius: 8px; padding: 10px; background: #f4f7fa; }
.box-title { font-weight: 700; font-size: .82rem; color: #1d3557; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 6px; }
.prog-code { font: 13px ui-monospace, monospace; color: #1d3557; margin: 0; white-space: pre; line-height: 1.5; }
.phase-label { font-size: .92rem; font-weight: 600; color: #457b9d; min-height: 1.3em; margin-bottom: 6px; }
.pred-list { font-size: .85rem; color: #333; min-height: 1em; }
.pred-list span { display: inline-block; background: #dce8f3; border: 1px solid #a8c4d8; border-radius: 4px; padding: 1px 6px; margin: 2px 2px; }
.cex-box { font-size: .85rem; color: #c92f3c; background: #fdecea; border: 1px solid #e8aaaa; border-radius: 6px; padding: 6px 8px; margin-top: 6px; }
.cex-box.spurious { color: #7b4200; background: #fff3e0; border-color: #f4a03a; }
.result-box { font-size: .95rem; font-weight: 700; border-radius: 6px; padding: 7px 10px; margin-top: 8px; text-align: center; }
.result-box.safe { color: #0a7d33; background: #e6f4ea; border: 1px solid #7bcf9a; }
.result-box.bug { color: #c92f3c; background: #fdecea; border: 1px solid #e8aaaa; }
.hidden { display: none !important; }
.btn-row { display: flex; gap: 8px; margin-bottom: 8px; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.log { font: 13px ui-monospace, monospace; background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 7px; padding: 8px 10px; max-height: 130px; overflow-y: auto; }
.log div { padding: 1px 0; line-height: 1.45; }
.log .log-ok { color: #0a7d33; }
.log .log-warn { color: #7b4200; }
.log .log-err { color: #c92f3c; }
.log .log-info { color: #1d3557; }
// CEGAR loop visualizer — pre-scripted steps show abstract/check/simulate/refine
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The concrete program we want to verify
// Program (pseudocode):
// x = 0; y = 0;
// while (x < 2) { x++; y += x; }
// assert(y != 3); // the assertion is SAFE: y will be 1+2=3, so this FAILS
// We flip: the property to VERIFY is "y never equals 3".
// The real program reaches y==3, so the verifier should find a real bug.
// But with no predicates the abstract model immediately reports a path,
// which is first checked as spurious until the predicates are precise enough.
const PROG_LINES = [
'x = 0; y = 0;',
'while (x < 2) {',
' x = x + 1;',
' y = y + x;',
'}',
'assert(y ≠ 3);'
];
// All predicates the loop may discover, in order
// Candidate predicates in order of discovery by the CEGAR loop.
const ALL_PREDICATES = [
{ id: 'p0', label: 'x ≥ 1', test: (s) => s.x >= 1 },
{ id: 'p1', label: 'x ≥ 2', test: (s) => s.x >= 2 },
{ id: 'p2', label: 'y ≥ 1', test: (s) => s.y >= 1 },
{ id: 'p3', label: 'y = 3', test: (s) => s.y === 3 }
];
// Pre-scripted CEGAR steps
// Pre-scripted CEGAR steps. Each step has a phase, message, and effect.
// Phases: "abstract", "check", "simulate", "refine", "done"
const STEPS = [
{
phase: 'Phase: Abstract',
log: '[abstract] No predicates — all states collapsed to one abstract state.',
logCls: 'log-info',
cex: null,
spurious: null,
addPred: null
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model reached y=3 violation via any path.',
logCls: 'log-err',
cex: 'abstract path: * -> y=3',
spurious: null,
addPred: null
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path x=0,y=0 -> assert: cannot reach y=3 with x staying 0 — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: * -> y=3',
spurious: true,
addPred: 'p0'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking whether x has incremented.', { pred: ALL_PREDICATES[0].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p0'
},
{
phase: 'Phase: Model Check',
log: '[check] Still a path in abstract model reaching y=3.',
logCls: 'log-err',
cex: 'abstract path: x<1 -> x<2 -> y=3',
spurious: null,
addPred: 'p0'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path requires x<2 forever — impossible in concrete program — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: x<1 -> x<2 -> y=3',
spurious: true,
addPred: 'p1'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking loop termination.', { pred: ALL_PREDICATES[1].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p1'
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model still admits path to y=3.',
logCls: 'log-err',
cex: 'abstract path: x<1 -> x>=1,x<2 -> y=3',
spurious: null,
addPred: 'p1'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path needs y to jump directly without accumulation — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: x<1 -> x>=1,x<2 -> y=3',
spurious: true,
addPred: 'p2'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking whether y has grown.', { pred: ALL_PREDICATES[2].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p2'
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model finds path: x goes 0->1->2, y accumulates 0->1->3.',
logCls: 'log-err',
cex: 'path: x=0,y=0 -> x=1,y=1 -> x=2,y=3',
spurious: null,
addPred: 'p2'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Concrete execution: x=0,y=0 -> x=1,y=1 -> x=2,y=3 -> assert(3!=3) FAILS. REAL BUG!',
logCls: 'log-ok',
cex: 'path: x=0,y=0 -> x=1,y=1 -> x=2,y=3',
spurious: false,
addPred: 'p3'
},
{
phase: 'Phase: Done',
log: '[done] Bug confirmed: y reaches 3 on the concrete path. Property violated.',
logCls: 'log-err',
cex: null,
spurious: null,
addPred: 'p3',
result: 'bug'
}
];
// Current step index and active predicate set
let stepIdx = 0;
const activePreds = new Set();
function getActivePredLabels() {
if (activePreds.size === 0) return '(none — coarsest abstraction)';
return [...activePreds].map(id => {
const p = ALL_PREDICATES.find(p => p.id === id);
return p ? p.label : id;
}).join(', ');
}
function renderProg() {
document.getElementById('prog-code').textContent = PROG_LINES.join('\n');
}
function addLog(msg, cls) {
const el = document.createElement('div');
el.className = cls || '';
el.textContent = msg;
const log = document.getElementById('log');
log.appendChild(el);
log.scrollTop = log.scrollHeight;
}
function applyStep(step) {
// Apply one CEGAR step: update predicates, phase label, counterexample box, result
if (step.addPred) activePreds.add(step.addPred);
document.getElementById('phase-label').textContent = step.phase;
const predEl = document.getElementById('pred-list');
const labels = getActivePredLabels();
if (activePreds.size === 0) {
predEl.innerHTML = '<em>' + '(none — coarsest abstraction)' + '</em>';
} else {
predEl.innerHTML = [...activePreds].map(id => {
const p = ALL_PREDICATES.find(p => p.id === id);
return '<span>' + (p ? p.label : id) + '</span>';
}).join('');
}
const cexBox = document.getElementById('cex-box');
if (step.cex) {
cexBox.classList.remove('hidden');
if (step.spurious === true) {
cexBox.className = 'cex-box spurious';
cexBox.textContent = 'Spurious counterexample: ' + step.cex;
} else if (step.spurious === false) {
cexBox.className = 'cex-box';
cexBox.textContent = 'Real counterexample: ' + step.cex;
} else {
cexBox.className = 'cex-box';
cexBox.textContent = step.cex;
}
} else {
cexBox.classList.add('hidden');
}
const resultBox = document.getElementById('result-box');
if (step.result === 'safe') {
resultBox.classList.remove('hidden');
resultBox.className = 'result-box safe';
resultBox.textContent = 'SAFE: property verified!';
} else if (step.result === 'bug') {
resultBox.classList.remove('hidden');
resultBox.className = 'result-box bug';
resultBox.textContent = 'BUG FOUND: counterexample is real.';
} else {
resultBox.classList.add('hidden');
}
addLog(step.log, step.logCls);
}
function doStep() {
if (stepIdx >= STEPS.length) return;
applyStep(STEPS[stepIdx]);
stepIdx++;
if (stepIdx >= STEPS.length) {
document.getElementById('btn-step').disabled = true;
}
}
function doReset() {
stepIdx = 0;
activePreds.clear();
document.getElementById('phase-label').textContent = 'Ready — press Step to start';
const predEl = document.getElementById('pred-list');
predEl.innerHTML = '<em>(none — coarsest abstraction)</em>';
document.getElementById('cex-box').classList.add('hidden');
document.getElementById('result-box').classList.add('hidden');
document.getElementById('log').innerHTML = '';
document.getElementById('btn-step').disabled = false;
}
document.getElementById('btn-step').addEventListener('click', doStep);
document.getElementById('btn-reset').addEventListener('click', doReset);
renderProg();
// Initialize display
document.getElementById('phase-label').textContent = 'Ready — press Step to start';
document.getElementById('pred-list').innerHTML = '<em>(none — coarsest abstraction)</em>';
<!-- CEGAR demo: abstract model on left, loop state on right -->
<div class="cegar-wrap">
<div class="prog-box">
<div class="box-title">Program (pseudocode)</div>
<pre class="prog-code" id="prog-code"></pre>
</div>
<div class="loop-box">
<div class="box-title">CEGAR Loop State</div>
<div id="phase-label" class="phase-label"></div>
<div id="pred-list" class="pred-list"></div>
<div id="cex-box" class="cex-box hidden"></div>
<div id="result-box" class="result-box hidden"></div>
</div>
</div>
<div class="btn-row">
<button id="btn-step" type="button">Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="log" class="log"></div>
/* Styles for the CEGAR loop visualizer */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.cegar-wrap { display: flex; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.prog-box, .loop-box { flex: 1 1 180px; border: 1px solid #cdd9e3; border-radius: 8px; padding: 10px; background: #f4f7fa; }
.box-title { font-weight: 700; font-size: .82rem; color: #1d3557; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 6px; }
.prog-code { font: 13px ui-monospace, monospace; color: #1d3557; margin: 0; white-space: pre; line-height: 1.5; }
.phase-label { font-size: .92rem; font-weight: 600; color: #457b9d; min-height: 1.3em; margin-bottom: 6px; }
.pred-list { font-size: .85rem; color: #333; min-height: 1em; }
.pred-list span { display: inline-block; background: #dce8f3; border: 1px solid #a8c4d8; border-radius: 4px; padding: 1px 6px; margin: 2px 2px; }
.cex-box { font-size: .85rem; color: #c92f3c; background: #fdecea; border: 1px solid #e8aaaa; border-radius: 6px; padding: 6px 8px; margin-top: 6px; }
.cex-box.spurious { color: #7b4200; background: #fff3e0; border-color: #f4a03a; }
.result-box { font-size: .95rem; font-weight: 700; border-radius: 6px; padding: 7px 10px; margin-top: 8px; text-align: center; }
.result-box.safe { color: #0a7d33; background: #e6f4ea; border: 1px solid #7bcf9a; }
.result-box.bug { color: #c92f3c; background: #fdecea; border: 1px solid #e8aaaa; }
.hidden { display: none !important; }
.btn-row { display: flex; gap: 8px; margin-bottom: 8px; }
button { font: 600 14px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.log { font: 13px ui-monospace, monospace; background: #f4f7fa; border: 1px solid #cdd9e3; border-radius: 7px; padding: 8px 10px; max-height: 130px; overflow-y: auto; }
.log div { padding: 1px 0; line-height: 1.45; }
.log .log-ok { color: #0a7d33; }
.log .log-warn { color: #7b4200; }
.log .log-err { color: #c92f3c; }
.log .log-info { color: #1d3557; }
// CEGAR loop visualizer — pre-scripted steps show abstract/check/simulate/refine
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// The concrete program we want to verify
// Program (pseudocode):
// x = 0; y = 0;
// while (x < 2) { x++; y += x; }
// assert(y != 3); // the assertion is SAFE: y will be 1+2=3, so this FAILS
// We flip: the property to VERIFY is "y never equals 3".
// The real program reaches y==3, so the verifier should find a real bug.
// But with no predicates the abstract model immediately reports a path,
// which is first checked as spurious until the predicates are precise enough.
const PROG_LINES = [
'x = 0; y = 0;',
'while (x < 2) {',
' x = x + 1;',
' y = y + x;',
'}',
'assert(y ≠ 3);'
];
// All predicates the loop may discover, in order
// Candidate predicates in order of discovery by the CEGAR loop.
const ALL_PREDICATES = [
{ id: 'p0', label: 'x ≥ 1', test: (s) => s.x >= 1 },
{ id: 'p1', label: 'x ≥ 2', test: (s) => s.x >= 2 },
{ id: 'p2', label: 'y ≥ 1', test: (s) => s.y >= 1 },
{ id: 'p3', label: 'y = 3', test: (s) => s.y === 3 }
];
// Pre-scripted CEGAR steps
// Pre-scripted CEGAR steps. Each step has a phase, message, and effect.
// Phases: "abstract", "check", "simulate", "refine", "done"
const STEPS = [
{
phase: 'Phase: Abstract',
log: '[abstract] No predicates — all states collapsed to one abstract state.',
logCls: 'log-info',
cex: null,
spurious: null,
addPred: null
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model reached y=3 violation via any path.',
logCls: 'log-err',
cex: 'abstract path: * -> y=3',
spurious: null,
addPred: null
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path x=0,y=0 -> assert: cannot reach y=3 with x staying 0 — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: * -> y=3',
spurious: true,
addPred: 'p0'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking whether x has incremented.', { pred: ALL_PREDICATES[0].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p0'
},
{
phase: 'Phase: Model Check',
log: '[check] Still a path in abstract model reaching y=3.',
logCls: 'log-err',
cex: 'abstract path: x<1 -> x<2 -> y=3',
spurious: null,
addPred: 'p0'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path requires x<2 forever — impossible in concrete program — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: x<1 -> x<2 -> y=3',
spurious: true,
addPred: 'p1'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking loop termination.', { pred: ALL_PREDICATES[1].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p1'
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model still admits path to y=3.',
logCls: 'log-err',
cex: 'abstract path: x<1 -> x>=1,x<2 -> y=3',
spurious: null,
addPred: 'p1'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Path needs y to jump directly without accumulation — SPURIOUS.',
logCls: 'log-warn',
cex: 'abstract path: x<1 -> x>=1,x<2 -> y=3',
spurious: true,
addPred: 'p2'
},
{
phase: 'Phase: Refine Abstraction',
log: i18nFmt('[refine] Added predicate: %pred% — now tracking whether y has grown.', { pred: ALL_PREDICATES[2].label }),
logCls: 'log-info',
cex: null,
spurious: null,
addPred: 'p2'
},
{
phase: 'Phase: Model Check',
log: '[check] Abstract model finds path: x goes 0->1->2, y accumulates 0->1->3.',
logCls: 'log-err',
cex: 'path: x=0,y=0 -> x=1,y=1 -> x=2,y=3',
spurious: null,
addPred: 'p2'
},
{
phase: 'Phase: Simulate Counterexample',
log: '[simulate] Concrete execution: x=0,y=0 -> x=1,y=1 -> x=2,y=3 -> assert(3!=3) FAILS. REAL BUG!',
logCls: 'log-ok',
cex: 'path: x=0,y=0 -> x=1,y=1 -> x=2,y=3',
spurious: false,
addPred: 'p3'
},
{
phase: 'Phase: Done',
log: '[done] Bug confirmed: y reaches 3 on the concrete path. Property violated.',
logCls: 'log-err',
cex: null,
spurious: null,
addPred: 'p3',
result: 'bug'
}
];
// Current step index and active predicate set
let stepIdx = 0;
const activePreds = new Set();
function getActivePredLabels() {
if (activePreds.size === 0) return '(none — coarsest abstraction)';
return [...activePreds].map(id => {
const p = ALL_PREDICATES.find(p => p.id === id);
return p ? p.label : id;
}).join(', ');
}
function renderProg() {
document.getElementById('prog-code').textContent = PROG_LINES.join('\n');
}
function addLog(msg, cls) {
const el = document.createElement('div');
el.className = cls || '';
el.textContent = msg;
const log = document.getElementById('log');
log.appendChild(el);
log.scrollTop = log.scrollHeight;
}
function applyStep(step) {
// Apply one CEGAR step: update predicates, phase label, counterexample box, result
if (step.addPred) activePreds.add(step.addPred);
document.getElementById('phase-label').textContent = step.phase;
const predEl = document.getElementById('pred-list');
const labels = getActivePredLabels();
if (activePreds.size === 0) {
predEl.innerHTML = '<em>' + '(none — coarsest abstraction)' + '</em>';
} else {
predEl.innerHTML = [...activePreds].map(id => {
const p = ALL_PREDICATES.find(p => p.id === id);
return '<span>' + (p ? p.label : id) + '</span>';
}).join('');
}
const cexBox = document.getElementById('cex-box');
if (step.cex) {
cexBox.classList.remove('hidden');
if (step.spurious === true) {
cexBox.className = 'cex-box spurious';
cexBox.textContent = 'Spurious counterexample: ' + step.cex;
} else if (step.spurious === false) {
cexBox.className = 'cex-box';
cexBox.textContent = 'Real counterexample: ' + step.cex;
} else {
cexBox.className = 'cex-box';
cexBox.textContent = step.cex;
}
} else {
cexBox.classList.add('hidden');
}
const resultBox = document.getElementById('result-box');
if (step.result === 'safe') {
resultBox.classList.remove('hidden');
resultBox.className = 'result-box safe';
resultBox.textContent = 'SAFE: property verified!';
} else if (step.result === 'bug') {
resultBox.classList.remove('hidden');
resultBox.className = 'result-box bug';
resultBox.textContent = 'BUG FOUND: counterexample is real.';
} else {
resultBox.classList.add('hidden');
}
addLog(step.log, step.logCls);
}
function doStep() {
if (stepIdx >= STEPS.length) return;
applyStep(STEPS[stepIdx]);
stepIdx++;
if (stepIdx >= STEPS.length) {
document.getElementById('btn-step').disabled = true;
}
}
function doReset() {
stepIdx = 0;
activePreds.clear();
document.getElementById('phase-label').textContent = 'Ready — press Step to start';
const predEl = document.getElementById('pred-list');
predEl.innerHTML = '<em>(none — coarsest abstraction)</em>';
document.getElementById('cex-box').classList.add('hidden');
document.getElementById('result-box').classList.add('hidden');
document.getElementById('log').innerHTML = '';
document.getElementById('btn-step').disabled = false;
}
document.getElementById('btn-step').addEventListener('click', doStep);
document.getElementById('btn-reset').addEventListener('click', doReset);
renderProg();
// Initialize display
document.getElementById('phase-label').textContent = 'Ready — press Step to start';
document.getElementById('pred-list').innerHTML = '<em>(none — coarsest abstraction)</em>';