Here is a tiny 3-SAT instance: a handful of clauses, each demanding that at least one of three literals be true. The only sure way to settle satisfiability is to try assignments of true/false to the variables. Drag the slider to change how many variables n there are, and the solver tries every combination.
<p class="hint">A random <b>3-SAT</b> instance with <span id="nlabel">8</span> variables.
Brute force is the only sure method: try every true/false assignment and check the
clauses. Each extra variable <b>doubles</b> the work — the curve you build below is
the Exponential Time Hypothesis made visible.</p>
<div class="ctl">
<label>Variables n: <input id="n" type="range" min="2" max="20" value="8"></label>
<span class="val" id="nval">8</span>
</div>
<div class="btns">
<button id="solve" type="button">Brute-force solve</button>
<button id="reset" type="button" class="ghost">Clear chart</button>
</div>
<div class="status" id="status">Pick n, then brute-force solve.</div>
<div class="meter"><div class="bar" id="bar"></div></div>
<div class="chart" id="chart"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.ctl { display: flex; align-items: center; gap: .6rem; margin: .3rem 0; font-size: .9rem; }
.ctl input[type=range] { flex: 1; max-width: 260px; }
.val { font: 700 16px ui-monospace, monospace; color: #1d3557; min-width: 2ch; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .4rem 0; }
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; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.meter { height: 10px; background: #e8eef3; border-radius: 6px; overflow: hidden; margin: .3rem 0 .8rem; }
.bar { height: 100%; width: 0; background: #e63946; transition: width .25s; }
.chart { display: flex; align-items: flex-end; gap: 4px; height: 130px; border-bottom: 2px solid #cdd9e3; padding-bottom: 2px; }
.col { flex: 1; min-width: 6px; background: #1d3557; border-radius: 3px 3px 0 0; position: relative; }
.col span { position: absolute; top: -16px; left: 50%; transform: translateX(-50%);
font: 600 10px ui-monospace, monospace; color: #1d3557; white-space: nowrap; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Exponential Time Hypothesis demo. We brute-force 3-SAT: the only sure way to
// decide satisfiability is to try all 2^n assignments. The number of assignments
// checked is therefore (worst case) 2^n, doubling with every added variable.
// ETH (Impagliazzo-Paturi, 2001) conjectures no algorithm beats 2^(c*n) for some c>0.
function makeClauses(n){
// A small random 3-SAT formula over n variables: ~4n clauses of 3 literals each.
const m = Math.max(3, Math.round(4 * n)), cl = [];
for(let i=0;i<m;i++){ const c=[]; while(c.length<3){
const v = Math.floor(Math.random()*n), lit = (v+1)*(Math.random()<0.5?1:-1);
if(!c.includes(lit) && !c.includes(-lit)) c.push(lit); } cl.push(c); }
return cl;
}
// Evaluate a CNF formula under an assignment encoded as the bits of `mask`.
function satisfies(clauses, mask){
for(const c of clauses){ let ok=false;
for(const lit of c){ const v=Math.abs(lit)-1, val=(mask>>v)&1;
if((lit>0 && val===1) || (lit<0 && val===0)){ ok=true; break; } }
if(!ok) return false; }
return true;
}
// Brute force: scan masks 0..2^n-1, count how many we examined before deciding.
function bruteForce(n, clauses){
const total = Math.pow(2,n); let checked=0, sat=false, witness=-1;
for(let mask=0; mask<total; mask++){ checked++;
if(satisfies(clauses, mask)){ sat=true; witness=mask; break; } }
return { checked, total, sat, witness };
}
const results = []; // {n, checked}
const $ = id => document.getElementById(id);
function setStatus(t,cls){ const s=$('status'); s.textContent=t; s.className='status '+cls; }
function drawChart(){
const c=$('chart'); c.innerHTML='';
if(!results.length) return;
const max = Math.max(...results.map(r=>r.checked));
for(const r of results){
const col=document.createElement('div'); col.className='col';
const h = Math.max(4, Math.round(120 * r.checked / max));
col.style.height = h+'px';
const lab=document.createElement('span'); lab.textContent = 'n='+r.n;
col.appendChild(lab); c.appendChild(col);
}
}
$('n').addEventListener('input', e=>{ const v=e.target.value; $('nval').textContent=v; $('nlabel').textContent=v; });
$('solve').addEventListener('click', ()=>{
const n = parseInt($('n').value,10);
const clauses = makeClauses(n);
const r = bruteForce(n, clauses);
$('bar').style.width = Math.min(100, 100 * r.checked / r.total) + '%';
const verdict = r.sat ? 'SATISFIABLE' : 'UNSATISFIABLE';
setStatus(fmt('n=%n%: checked %checked% of %total% assignments — %verdict%.', { n: n, checked: r.checked.toLocaleString(), total: r.total.toLocaleString(), verdict: verdict }), r.sat?'ok':'bad');
results.push({ n, checked: r.checked });
results.sort((a,b)=>a.n-b.n);
drawChart();
console.log('n='+n, 'assignments tried:', r.checked, '/ 2^'+n+'='+r.total, verdict);
});
$('reset').addEventListener('click', ()=>{ results.length=0; drawChart(); $('bar').style.width='0';
setStatus('Pick n, then brute-force solve.',''); });
<p class="hint">A random <b>3-SAT</b> instance with <span id="nlabel">8</span> variables.
Brute force is the only sure method: try every true/false assignment and check the
clauses. Each extra variable <b>doubles</b> the work — the curve you build below is
the Exponential Time Hypothesis made visible.</p>
<div class="ctl">
<label>Variables n: <input id="n" type="range" min="2" max="20" value="8"></label>
<span class="val" id="nval">8</span>
</div>
<div class="btns">
<button id="solve" type="button">Brute-force solve</button>
<button id="reset" type="button" class="ghost">Clear chart</button>
</div>
<div class="status" id="status">Pick n, then brute-force solve.</div>
<div class="meter"><div class="bar" id="bar"></div></div>
<div class="chart" id="chart"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.ctl { display: flex; align-items: center; gap: .6rem; margin: .3rem 0; font-size: .9rem; }
.ctl input[type=range] { flex: 1; max-width: 260px; }
.val { font: 700 16px ui-monospace, monospace; color: #1d3557; min-width: 2ch; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .4rem 0; }
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; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.meter { height: 10px; background: #e8eef3; border-radius: 6px; overflow: hidden; margin: .3rem 0 .8rem; }
.bar { height: 100%; width: 0; background: #e63946; transition: width .25s; }
.chart { display: flex; align-items: flex-end; gap: 4px; height: 130px; border-bottom: 2px solid #cdd9e3; padding-bottom: 2px; }
.col { flex: 1; min-width: 6px; background: #1d3557; border-radius: 3px 3px 0 0; position: relative; }
.col span { position: absolute; top: -16px; left: 50%; transform: translateX(-50%);
font: 600 10px ui-monospace, monospace; color: #1d3557; white-space: nowrap; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Exponential Time Hypothesis demo. We brute-force 3-SAT: the only sure way to
// decide satisfiability is to try all 2^n assignments. The number of assignments
// checked is therefore (worst case) 2^n, doubling with every added variable.
// ETH (Impagliazzo-Paturi, 2001) conjectures no algorithm beats 2^(c*n) for some c>0.
function makeClauses(n){
// A small random 3-SAT formula over n variables: ~4n clauses of 3 literals each.
const m = Math.max(3, Math.round(4 * n)), cl = [];
for(let i=0;i<m;i++){ const c=[]; while(c.length<3){
const v = Math.floor(Math.random()*n), lit = (v+1)*(Math.random()<0.5?1:-1);
if(!c.includes(lit) && !c.includes(-lit)) c.push(lit); } cl.push(c); }
return cl;
}
// Evaluate a CNF formula under an assignment encoded as the bits of `mask`.
function satisfies(clauses, mask){
for(const c of clauses){ let ok=false;
for(const lit of c){ const v=Math.abs(lit)-1, val=(mask>>v)&1;
if((lit>0 && val===1) || (lit<0 && val===0)){ ok=true; break; } }
if(!ok) return false; }
return true;
}
// Brute force: scan masks 0..2^n-1, count how many we examined before deciding.
function bruteForce(n, clauses){
const total = Math.pow(2,n); let checked=0, sat=false, witness=-1;
for(let mask=0; mask<total; mask++){ checked++;
if(satisfies(clauses, mask)){ sat=true; witness=mask; break; } }
return { checked, total, sat, witness };
}
const results = []; // {n, checked}
const $ = id => document.getElementById(id);
function setStatus(t,cls){ const s=$('status'); s.textContent=t; s.className='status '+cls; }
function drawChart(){
const c=$('chart'); c.innerHTML='';
if(!results.length) return;
const max = Math.max(...results.map(r=>r.checked));
for(const r of results){
const col=document.createElement('div'); col.className='col';
const h = Math.max(4, Math.round(120 * r.checked / max));
col.style.height = h+'px';
const lab=document.createElement('span'); lab.textContent = 'n='+r.n;
col.appendChild(lab); c.appendChild(col);
}
}
$('n').addEventListener('input', e=>{ const v=e.target.value; $('nval').textContent=v; $('nlabel').textContent=v; });
$('solve').addEventListener('click', ()=>{
const n = parseInt($('n').value,10);
const clauses = makeClauses(n);
const r = bruteForce(n, clauses);
$('bar').style.width = Math.min(100, 100 * r.checked / r.total) + '%';
const verdict = r.sat ? 'SATISFIABLE' : 'UNSATISFIABLE';
setStatus(fmt('n=%n%: checked %checked% of %total% assignments — %verdict%.', { n: n, checked: r.checked.toLocaleString(), total: r.total.toLocaleString(), verdict: verdict }), r.sat?'ok':'bad');
results.push({ n, checked: r.checked });
results.sort((a,b)=>a.n-b.n);
drawChart();
console.log('n='+n, 'assignments tried:', r.checked, '/ 2^'+n+'='+r.total, verdict);
});
$('reset').addEventListener('click', ()=>{ results.length=0; drawChart(); $('bar').style.width='0';
setStatus('Pick n, then brute-force solve.',''); });
Watch the counter. Each extra variable doubles the number of assignments to check: n variables means up to 2n tries. Add ten variables and the work grows by a factor of a thousand; add twenty and it's a million. The ETH says this wall is real — that for 3-SAT, no algorithm can sneak the exponent below a positive constant times n.
Comments
Loading comments...