The demo below shows a tiny transition system with four states. The red state is bad; the green state is the initial state. IC3 maintains a sequence of frames F0,F1,F2,…; each frame is a set of states that are not yet known to reach bad.
<!-- IC3/PDR demo: transition system graph + frame list -->
<div class="layout">
<div class="graph-panel">
<h3 class="panel-title">Transition System</h3>
<svg id="graph" viewBox="0 0 340 240" width="340" height="240" aria-label="Transition system graph with states S0 to S3"></svg>
</div>
<div class="frames-panel">
<h3 class="panel-title">IC3 Frames</h3>
<div id="frames-list" class="frames-list"></div>
</div>
</div>
<div class="log-area">
<div id="log" class="log"></div>
</div>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
/* IC3/PDR demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.layout { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
.graph-panel, .frames-panel { flex: 1 1 160px; }
.panel-title { font-size: .85rem; font-weight: 700; margin: 0 0 .4rem; color: #1d3557; text-transform: uppercase; letter-spacing: .04em; }
svg { display: block; border: 1px solid #dde3ea; border-radius: 10px; background: #f8fafc; }
.frames-list { display: flex; flex-direction: column; gap: .35rem; }
.frame-row { font-size: .82rem; padding: .3rem .5rem; border-radius: 6px; background: #eef2f7; border-left: 3px solid #1d3557; }
.frame-row.inductive { border-left-color: #0a7d33; background: #e8f7ed; }
.log-area { margin: .6rem 0 .4rem; max-height: 90px; overflow-y: auto; border: 1px solid #dde3ea; border-radius: 8px; background: #f8fafc; padding: .35rem .6rem; }
.log { font-size: .78rem; color: #444; line-height: 1.55; }
.log .entry { border-bottom: 1px solid #eee; padding: .15rem 0; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
/* Node color classes applied via JS fill attribute */
.node-circle { stroke-width: 2; }
.node-label { font: 700 13px ui-monospace, monospace; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// IC3 / Property-Directed Reachability demo.
// States: S0 (initial, green), S1, S2, S3 (bad, red).
// IC3 builds frames F0..Fn, blocking predecessors of bad and pushing clauses forward.
// All states in the system
// S0=initial, S1=intermediate, S2=loop, S3=bad
const STATES = ['S0','S1','S2','S3'];
const INIT = 'S0'; // initial state
const BAD = 'S3'; // bad (unsafe) state
// Transition relation: TRANS[s] = list of successors of s
const TRANS = { S0:['S1','S2'], S1:['S2'], S2:['S1'], S3:['S3'] };
// SVG positions for each state node
const POS = { S0:[60,120], S1:[160,50], S2:[160,190], S3:[280,120] };
// Node fill colors
const COLOR = {
default:'#c9ccd1', init:'#2ecc71', bad:'#e63946',
blocked:'#f0a500', inductive:'#1d3557'
};
// IC3 frames: frames[i] is the set of states not yet known to reach bad in i steps
// Initially every frame holds all states; we progressively remove states.
// F0 = {S0,S1,S2,S3} (all states). Each step blocks predecessors of bad.
let frames = []; // frames[i] = Set of states still in frame i
let obligations = []; // Stack of (frame, state) pairs that need to be blocked
let done = false;
function allStates(){ return new Set(STATES); }
function predecessors(stateSet){
// Return all states that can reach any state in stateSet in one step
const preds = new Set();
for(const s of STATES){
if(TRANS[s].some(t => stateSet.has(t))) preds.add(s);
}
return preds;
}
function intersect(a,b){ return new Set([...a].filter(x=>b.has(x))); }
function setMinus(a,b){ return new Set([...a].filter(x=>!b.has(x))); }
function setsEqual(a,b){ return a.size===b.size && [...a].every(x=>b.has(x)); }
function initIC3(){
// Initialize IC3: two frames, seed obligations from predecessors of BAD
frames = [allStates(), allStates()]; // F0 and F1 start as all states
obligations = [];
// Seed: block predecessors of BAD from F1
const predBad = predecessors(new Set([BAD]));
const toBlock = intersect(predBad, frames[1]);
for(const s of toBlock) obligations.push({frame:1, state:s});
done = false;
}
function logEntry(msg){
const d = document.getElementById('log');
const e = document.createElement('div'); e.className='entry'; e.textContent=msg;
d.appendChild(e); d.scrollTop=d.scrollHeight;
}
function renderGraph(){
const svg = document.getElementById('graph');
svg.innerHTML = '';
// Draw transition arrows
for(const s of STATES){
for(const t of TRANS[s]){
const [x1,y1]=POS[s]; const [x2,y2]=POS[t];
const dx=x2-x1,dy=y2-y1,len=Math.sqrt(dx*dx+dy*dy); if(len===0)continue;
const ux=dx/len,uy=dy/len,r=22;
const sx=x1+ux*r,sy=y1+uy*r,ex=x2-ux*r,ey=y2-uy*r;
const line=document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1',sx);line.setAttribute('y1',sy);
line.setAttribute('x2',ex);line.setAttribute('y2',ey);
line.setAttribute('stroke','#aab3bf');line.setAttribute('stroke-width','1.8');
line.setAttribute('marker-end','url(#arr)'); svg.appendChild(line);
}
}
// Draw state nodes
for(const s of STATES){
const [cx,cy]=POS[s];
// Pick fill color: init=green, bad=red, blocked=orange, default=gray
let fill = COLOR.default;
if(s===INIT) fill=COLOR.init;
if(s===BAD) fill=COLOR.bad;
const lastFrame = frames[frames.length-1];
if(lastFrame && !lastFrame.has(s) && s!==INIT && s!==BAD) fill=COLOR.blocked;
const circle=document.createElementNS('http://www.w3.org/2000/svg','circle');
circle.setAttribute('cx',cx);circle.setAttribute('cy',cy);circle.setAttribute('r',22);
circle.setAttribute('fill',fill);circle.setAttribute('stroke','#fff');
circle.setAttribute('stroke-width','2');circle.setAttribute('class','node-circle');
svg.appendChild(circle);
const text=document.createElementNS('http://www.w3.org/2000/svg','text');
text.setAttribute('x',cx);text.setAttribute('y',cy+5);
text.setAttribute('text-anchor','middle');text.setAttribute('fill','#fff');
text.setAttribute('class','node-label');text.textContent=s;
svg.appendChild(text);
}
// Arrowhead marker definition (prepend so arrows render on top)
const defs=document.createElementNS('http://www.w3.org/2000/svg','defs');
const marker=document.createElementNS('http://www.w3.org/2000/svg','marker');
marker.setAttribute('id','arr');marker.setAttribute('markerWidth','8');
marker.setAttribute('markerHeight','6');marker.setAttribute('refX','6');
marker.setAttribute('refY','3');marker.setAttribute('orient','auto');
const poly=document.createElementNS('http://www.w3.org/2000/svg','polygon');
poly.setAttribute('points','0 0, 8 3, 0 6');poly.setAttribute('fill','#aab3bf');
marker.appendChild(poly);defs.appendChild(marker);svg.insertBefore(defs,svg.firstChild);
}
function renderFrames(){
const el = document.getElementById('frames-list');
el.innerHTML = '';
frames.forEach((f,i)=>{
const div=document.createElement('div');
const isInd = i>0 && setsEqual(frames[i],frames[i-1]);
div.className = 'frame-row' + (isInd?' inductive':'');
const stateList = [...f].join(', ') || '∅';
div.textContent = 'F'+i+': {'+stateList+'}' + (isInd ? ' ✓' : '');
el.appendChild(div);
});
}
function setStatus(msg,cls){
const s=document.getElementById('status');
s.textContent=msg; s.className='status '+(cls||'');
}
function step(){
if(done){ setStatus(`Already proved — reset to start over.`,'ok'); return; }
if(obligations.length===0){
// No obligations left: try to push blocked states to earlier frames
// A state blocked in frame i can be removed from frame i+1 if it stays blocked
let pushed = false;
for(let i=1;i<frames.length-1;i++){
const fi=frames[i], fi1=frames[i+1];
// States not in frame i are blocked; remove them from i+1 if present
const blocked = setMinus(allStates(), fi);
for(const s of blocked){
if(fi1.has(s)){
fi1.delete(s);
logEntry(fmt(`Pushed block of %s% from F%i% to F%i%%v%.`, { s: s, i: i, v: 1 }));
pushed=true;
}
}
}
// Check if any two adjacent frames are equal (inductive invariant found)
for(let i=1;i<frames.length;i++){
if(setsEqual(frames[i],frames[i-1])){
done=true;
logEntry(fmt(`Frame F%i% == frame F%v% — inductive invariant found. Safety proved!`, { i: i, v: i-1 }));
setStatus(`Safety proved! An inductive invariant was found.`,'ok');
document.getElementById('step').disabled=true;
renderFrames(); renderGraph(); return;
}
}
// No invariant yet: add a new frame and seed new obligations
frames.push(new Set(frames[frames.length-1]));
logEntry(fmt(`Extended to new frame F%v%.`, { v: frames.length-1 }));
const predBad=predecessors(new Set([BAD]));
const toBlock=intersect(predBad,frames[frames.length-1]);
for(const s of toBlock) obligations.push({frame:frames.length-1,state:s});
renderFrames(); renderGraph();
setStatus(fmt(`Added frame F%v%. New obligations seeded.`, { v: frames.length-1 }),'');
return;
}
// Process next obligation: block the state from the given frame
const {frame,state} = obligations.pop();
// Already removed from this frame; nothing to do
if(!frames[frame].has(state)){
logEntry(fmt(`State %state% already blocked in frame F%frame%.`, { state: state, frame: frame }));
renderFrames(); renderGraph();
return;
}
// Block: remove state from this frame
frames[frame].delete(state);
logEntry(fmt(`Blocked state %state% from frame F%frame%.`, { state: state, frame: frame }));
// Propagate backwards: predecessors of blocked state also need blocking in earlier frame
if(frame>1){
const preds=predecessors(new Set([state]));
const toBlock=intersect(preds,frames[frame-1]);
for(const s of toBlock){
obligations.push({frame:frame-1,state:s});
}
if(toBlock.size>0){
logEntry(fmt(`Propagating obligation to frame F%v%.`, { v: frame-1 }));
}
}
renderFrames(); renderGraph();
setStatus(fmt(`Blocked state %state% from frame F%frame%.`, { state: state, frame: frame }),'');
}
document.getElementById('step').addEventListener('click',step);
document.getElementById('reset').addEventListener('click',()=>{
initIC3();
document.getElementById('log').innerHTML='';
document.getElementById('step').disabled=false;
renderFrames(); renderGraph();
setStatus(`Reset. Click Step to run IC3.`,'');
});
initIC3();
renderFrames();
renderGraph();
setStatus(`Click Step to run IC3 step by step.`,'');
<!-- IC3/PDR demo: transition system graph + frame list -->
<div class="layout">
<div class="graph-panel">
<h3 class="panel-title">Transition System</h3>
<svg id="graph" viewBox="0 0 340 240" width="340" height="240" aria-label="Transition system graph with states S0 to S3"></svg>
</div>
<div class="frames-panel">
<h3 class="panel-title">IC3 Frames</h3>
<div id="frames-list" class="frames-list"></div>
</div>
</div>
<div class="log-area">
<div id="log" class="log"></div>
</div>
<div class="btns">
<button id="step" type="button">Step</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="status" class="status"></div>
/* IC3/PDR demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.layout { display: flex; gap: 1rem; align-items: flex-start; flex-wrap: wrap; }
.graph-panel, .frames-panel { flex: 1 1 160px; }
.panel-title { font-size: .85rem; font-weight: 700; margin: 0 0 .4rem; color: #1d3557; text-transform: uppercase; letter-spacing: .04em; }
svg { display: block; border: 1px solid #dde3ea; border-radius: 10px; background: #f8fafc; }
.frames-list { display: flex; flex-direction: column; gap: .35rem; }
.frame-row { font-size: .82rem; padding: .3rem .5rem; border-radius: 6px; background: #eef2f7; border-left: 3px solid #1d3557; }
.frame-row.inductive { border-left-color: #0a7d33; background: #e8f7ed; }
.log-area { margin: .6rem 0 .4rem; max-height: 90px; overflow-y: auto; border: 1px solid #dde3ea; border-radius: 8px; background: #f8fafc; padding: .35rem .6rem; }
.log { font-size: .78rem; color: #444; line-height: 1.55; }
.log .entry { border-bottom: 1px solid #eee; padding: .15rem 0; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .4rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
.status { font-size: 1rem; font-weight: 600; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
/* Node color classes applied via JS fill attribute */
.node-circle { stroke-width: 2; }
.node-label { font: 700 13px ui-monospace, monospace; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// IC3 / Property-Directed Reachability demo.
// States: S0 (initial, green), S1, S2, S3 (bad, red).
// IC3 builds frames F0..Fn, blocking predecessors of bad and pushing clauses forward.
// All states in the system
// S0=initial, S1=intermediate, S2=loop, S3=bad
const STATES = ['S0','S1','S2','S3'];
const INIT = 'S0'; // initial state
const BAD = 'S3'; // bad (unsafe) state
// Transition relation: TRANS[s] = list of successors of s
const TRANS = { S0:['S1','S2'], S1:['S2'], S2:['S1'], S3:['S3'] };
// SVG positions for each state node
const POS = { S0:[60,120], S1:[160,50], S2:[160,190], S3:[280,120] };
// Node fill colors
const COLOR = {
default:'#c9ccd1', init:'#2ecc71', bad:'#e63946',
blocked:'#f0a500', inductive:'#1d3557'
};
// IC3 frames: frames[i] is the set of states not yet known to reach bad in i steps
// Initially every frame holds all states; we progressively remove states.
// F0 = {S0,S1,S2,S3} (all states). Each step blocks predecessors of bad.
let frames = []; // frames[i] = Set of states still in frame i
let obligations = []; // Stack of (frame, state) pairs that need to be blocked
let done = false;
function allStates(){ return new Set(STATES); }
function predecessors(stateSet){
// Return all states that can reach any state in stateSet in one step
const preds = new Set();
for(const s of STATES){
if(TRANS[s].some(t => stateSet.has(t))) preds.add(s);
}
return preds;
}
function intersect(a,b){ return new Set([...a].filter(x=>b.has(x))); }
function setMinus(a,b){ return new Set([...a].filter(x=>!b.has(x))); }
function setsEqual(a,b){ return a.size===b.size && [...a].every(x=>b.has(x)); }
function initIC3(){
// Initialize IC3: two frames, seed obligations from predecessors of BAD
frames = [allStates(), allStates()]; // F0 and F1 start as all states
obligations = [];
// Seed: block predecessors of BAD from F1
const predBad = predecessors(new Set([BAD]));
const toBlock = intersect(predBad, frames[1]);
for(const s of toBlock) obligations.push({frame:1, state:s});
done = false;
}
function logEntry(msg){
const d = document.getElementById('log');
const e = document.createElement('div'); e.className='entry'; e.textContent=msg;
d.appendChild(e); d.scrollTop=d.scrollHeight;
}
function renderGraph(){
const svg = document.getElementById('graph');
svg.innerHTML = '';
// Draw transition arrows
for(const s of STATES){
for(const t of TRANS[s]){
const [x1,y1]=POS[s]; const [x2,y2]=POS[t];
const dx=x2-x1,dy=y2-y1,len=Math.sqrt(dx*dx+dy*dy); if(len===0)continue;
const ux=dx/len,uy=dy/len,r=22;
const sx=x1+ux*r,sy=y1+uy*r,ex=x2-ux*r,ey=y2-uy*r;
const line=document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1',sx);line.setAttribute('y1',sy);
line.setAttribute('x2',ex);line.setAttribute('y2',ey);
line.setAttribute('stroke','#aab3bf');line.setAttribute('stroke-width','1.8');
line.setAttribute('marker-end','url(#arr)'); svg.appendChild(line);
}
}
// Draw state nodes
for(const s of STATES){
const [cx,cy]=POS[s];
// Pick fill color: init=green, bad=red, blocked=orange, default=gray
let fill = COLOR.default;
if(s===INIT) fill=COLOR.init;
if(s===BAD) fill=COLOR.bad;
const lastFrame = frames[frames.length-1];
if(lastFrame && !lastFrame.has(s) && s!==INIT && s!==BAD) fill=COLOR.blocked;
const circle=document.createElementNS('http://www.w3.org/2000/svg','circle');
circle.setAttribute('cx',cx);circle.setAttribute('cy',cy);circle.setAttribute('r',22);
circle.setAttribute('fill',fill);circle.setAttribute('stroke','#fff');
circle.setAttribute('stroke-width','2');circle.setAttribute('class','node-circle');
svg.appendChild(circle);
const text=document.createElementNS('http://www.w3.org/2000/svg','text');
text.setAttribute('x',cx);text.setAttribute('y',cy+5);
text.setAttribute('text-anchor','middle');text.setAttribute('fill','#fff');
text.setAttribute('class','node-label');text.textContent=s;
svg.appendChild(text);
}
// Arrowhead marker definition (prepend so arrows render on top)
const defs=document.createElementNS('http://www.w3.org/2000/svg','defs');
const marker=document.createElementNS('http://www.w3.org/2000/svg','marker');
marker.setAttribute('id','arr');marker.setAttribute('markerWidth','8');
marker.setAttribute('markerHeight','6');marker.setAttribute('refX','6');
marker.setAttribute('refY','3');marker.setAttribute('orient','auto');
const poly=document.createElementNS('http://www.w3.org/2000/svg','polygon');
poly.setAttribute('points','0 0, 8 3, 0 6');poly.setAttribute('fill','#aab3bf');
marker.appendChild(poly);defs.appendChild(marker);svg.insertBefore(defs,svg.firstChild);
}
function renderFrames(){
const el = document.getElementById('frames-list');
el.innerHTML = '';
frames.forEach((f,i)=>{
const div=document.createElement('div');
const isInd = i>0 && setsEqual(frames[i],frames[i-1]);
div.className = 'frame-row' + (isInd?' inductive':'');
const stateList = [...f].join(', ') || '∅';
div.textContent = 'F'+i+': {'+stateList+'}' + (isInd ? ' ✓' : '');
el.appendChild(div);
});
}
function setStatus(msg,cls){
const s=document.getElementById('status');
s.textContent=msg; s.className='status '+(cls||'');
}
function step(){
if(done){ setStatus(`Already proved — reset to start over.`,'ok'); return; }
if(obligations.length===0){
// No obligations left: try to push blocked states to earlier frames
// A state blocked in frame i can be removed from frame i+1 if it stays blocked
let pushed = false;
for(let i=1;i<frames.length-1;i++){
const fi=frames[i], fi1=frames[i+1];
// States not in frame i are blocked; remove them from i+1 if present
const blocked = setMinus(allStates(), fi);
for(const s of blocked){
if(fi1.has(s)){
fi1.delete(s);
logEntry(fmt(`Pushed block of %s% from F%i% to F%i%%v%.`, { s: s, i: i, v: 1 }));
pushed=true;
}
}
}
// Check if any two adjacent frames are equal (inductive invariant found)
for(let i=1;i<frames.length;i++){
if(setsEqual(frames[i],frames[i-1])){
done=true;
logEntry(fmt(`Frame F%i% == frame F%v% — inductive invariant found. Safety proved!`, { i: i, v: i-1 }));
setStatus(`Safety proved! An inductive invariant was found.`,'ok');
document.getElementById('step').disabled=true;
renderFrames(); renderGraph(); return;
}
}
// No invariant yet: add a new frame and seed new obligations
frames.push(new Set(frames[frames.length-1]));
logEntry(fmt(`Extended to new frame F%v%.`, { v: frames.length-1 }));
const predBad=predecessors(new Set([BAD]));
const toBlock=intersect(predBad,frames[frames.length-1]);
for(const s of toBlock) obligations.push({frame:frames.length-1,state:s});
renderFrames(); renderGraph();
setStatus(fmt(`Added frame F%v%. New obligations seeded.`, { v: frames.length-1 }),'');
return;
}
// Process next obligation: block the state from the given frame
const {frame,state} = obligations.pop();
// Already removed from this frame; nothing to do
if(!frames[frame].has(state)){
logEntry(fmt(`State %state% already blocked in frame F%frame%.`, { state: state, frame: frame }));
renderFrames(); renderGraph();
return;
}
// Block: remove state from this frame
frames[frame].delete(state);
logEntry(fmt(`Blocked state %state% from frame F%frame%.`, { state: state, frame: frame }));
// Propagate backwards: predecessors of blocked state also need blocking in earlier frame
if(frame>1){
const preds=predecessors(new Set([state]));
const toBlock=intersect(preds,frames[frame-1]);
for(const s of toBlock){
obligations.push({frame:frame-1,state:s});
}
if(toBlock.size>0){
logEntry(fmt(`Propagating obligation to frame F%v%.`, { v: frame-1 }));
}
}
renderFrames(); renderGraph();
setStatus(fmt(`Blocked state %state% from frame F%frame%.`, { state: state, frame: frame }),'');
}
document.getElementById('step').addEventListener('click',step);
document.getElementById('reset').addEventListener('click',()=>{
initIC3();
document.getElementById('log').innerHTML='';
document.getElementById('step').disabled=false;
renderFrames(); renderGraph();
setStatus(`Reset. Click Step to run IC3.`,'');
});
initIC3();
renderFrames();
renderGraph();
setStatus(`Click Step to run IC3 step by step.`,'');
Comments
Loading comments...