Toggle variables to build an assignment. The demo tells you which clauses are satisfied and whether the full formula is satisfied. Use Find all solutions to see every satisfying assignment — if the promise holds, you will find 0 or 1.
<p class="hint">A <b>Unique-SAT</b> instance: this formula is <em>promised</em> to have exactly one satisfying assignment or none. Toggle each variable to build an assignment. Satisfied clauses turn green. Use <b>Find all solutions</b> to verify the promise.</p>
<div id="formula"></div>
<div id="controls"></div>
<div class="status" id="status">Toggle variables to build an assignment.</div>
<div class="btns">
<button id="check" type="button">Check assignment</button>
<button id="findAll" type="button">Find all solutions</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="results"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
#formula { display: flex; flex-direction: column; gap: 6px; margin-bottom: .8rem; }
.clause { display: flex; align-items: center; gap: 6px; font: 600 15px ui-monospace, monospace;
padding: 6px 10px; border-radius: 8px; background: #f0f3f6; border: 1.5px solid #d4dde6;
transition: background .2s, border-color .2s; }
.clause.sat { background: #d4f0de; border-color: #5baa76; }
.clause.unsat { background: #fde6e6; border-color: #d16060; }
.clause .lbl { font-size: .75rem; font-weight: 400; color: #888; width: 48px; flex-shrink: 0; }
.lit { color: #1d3557; }
.lit.neg { color: #6d4c41; }
.lit.true { color: #1a7a45; }
.lit.false { color: #b71c1c; }
.op { color: #888; font-weight: 400; margin: 0 2px; }
#controls { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: .8rem; }
.var-btn { display: flex; flex-direction: column; align-items: center; gap: 3px; cursor: pointer;
padding: 8px 14px; border-radius: 10px; border: 1.5px solid #adb1b8;
background: #e8ecf0; font: 700 15px ui-monospace, monospace; user-select: none;
transition: all .15s; min-width: 64px; }
.var-btn .val-label { font-size: .72rem; font-weight: 600; letter-spacing: .04em; }
.var-btn.true { background: #1d3557; color: #fff; border-color: #1d3557; }
.var-btn.true .val-label { color: #90caf9; }
.var-btn.false .val-label { color: #888; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
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; }
#results { font-size: .85rem; line-height: 1.6; color: #333; }
#results .sol { font-family: ui-monospace, monospace; background: #eef6ee;
border-left: 3px solid #5baa76; padding: 3px 8px; border-radius: 4px; margin: 2px 0; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Promise problem demo: Unique-SAT
// Formula (3 variables x1,x2,x3 – 4 clauses) with EXACTLY ONE solution.
// Promise: input is guaranteed to have 0 or 1 satisfying assignments.
// Unique solution (verified by exhaustive search): x1=true, x2=false, x3=true.
//
// Clauses (CNF) — all non-unit, found by exhaustive clause search:
// C1: x1 ∨ x2 blocks FFF, FFT (x1=F,x2=F)
// C2: x1 ∨ ¬x2 blocks FTF, FTT (x1=F,x2=T)
// C3: ¬x1 ∨ x2 ∨ x3 blocks TFF (x1=T,x2=F,x3=F)
// C4: ¬x1 ∨ ¬x2 blocks TTF, TTT (x1=T,x2=T)
// Together: only mask 5 = TFT = (x1=T,x2=F,x3=T) survives.
const VARS = ['x₁', 'x₂', 'x₃'];
const CLAUSES = [
{ label: 'C₁', lits: [{v:0,neg:false},{v:1,neg:false}] }, // x1 ∨ x2
{ label: 'C₂', lits: [{v:0,neg:false},{v:1,neg:true}] }, // x1 ∨ ¬x2
{ label: 'C₃', lits: [{v:0,neg:true}, {v:1,neg:false},{v:2,neg:false}] }, // ¬x1 ∨ x2 ∨ x3
{ label: 'C₄', lits: [{v:0,neg:true}, {v:1,neg:true}] }, // ¬x1 ∨ ¬x2
];
const vals = [false, false, false];
function clauseSat(clause, assignment) {
return clause.lits.some(l => assignment[l.v] !== l.neg);
}
function formulaSat(assignment) {
return CLAUSES.every(c => clauseSat(c, assignment));
}
function findAllSolutions() {
const sols = [];
for (let mask = 0; mask < 8; mask++) {
const a = [!!(mask & 4), !!(mask & 2), !!(mask & 1)];
if (formulaSat(a)) sols.push(a);
}
return sols;
}
function renderFormula() {
const div = document.getElementById('formula');
div.innerHTML = '';
CLAUSES.forEach(clause => {
const sat = clauseSat(clause, vals);
const row = document.createElement('div');
row.className = 'clause' + (sat ? ' sat' : ' unsat');
let html = `<span class="lbl">${clause.label}:</span> (`;
clause.lits.forEach((l, i) => {
const v = vals[l.v];
const litVal = l.neg ? !v : v;
const cls = 'lit' + (l.neg ? ' neg' : '') + (litVal ? ' true' : ' false');
html += `<span class="${cls}">${l.neg ? '¬' : ''}${VARS[l.v]}</span>`;
if (i < clause.lits.length - 1) html += '<span class="op">∨</span>';
});
html += ')';
row.innerHTML = html;
div.appendChild(row);
});
}
function renderControls() {
const div = document.getElementById('controls');
div.innerHTML = '';
VARS.forEach((name, i) => {
const btn = document.createElement('div');
btn.className = 'var-btn ' + (vals[i] ? 'true' : 'false');
btn.innerHTML = `<span>${name}</span><span class="val-label">${vals[i] ? 'TRUE' : 'FALSE'}</span>`;
btn.addEventListener('click', () => {
vals[i] = !vals[i];
setStatus('Toggle variables to build an assignment.', '');
document.getElementById('results').innerHTML = '';
render();
});
div.appendChild(btn);
});
}
function render() { renderFormula(); renderControls(); }
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
document.getElementById('check').addEventListener('click', () => {
if (formulaSat(vals)) {
setStatus('✓ All clauses satisfied — this is the unique solution!', 'ok');
} else {
const n = CLAUSES.filter(c => !clauseSat(c, vals)).length;
setStatus(fmt('✗ %n% clause%v% not satisfied. Keep trying.', { n: n, v: n > 1 ? 's' : '' }), 'bad');
}
document.getElementById('results').innerHTML = '';
});
document.getElementById('findAll').addEventListener('click', () => {
const sols = findAllSolutions();
const res = document.getElementById('results');
if (sols.length === 0) {
setStatus('No satisfying assignments found.', 'bad');
res.innerHTML = '<em>Promise holds: 0 solutions.</em>';
} else if (sols.length === 1) {
setStatus('✓ Exactly 1 solution — the promise holds!', 'ok');
const row = document.createElement('div');
row.className = 'sol';
row.textContent = fmt('Solution: %map%', { map: VARS.map((n, i) => n + '=' + (sols[0][i] ? 'T' : 'F')).join(', ') });
res.innerHTML = '<strong>All satisfying assignments (checked all 8):</strong><br>';
res.appendChild(row);
console.log('Unique solution:', VARS.map((n,i)=>n+'='+(sols[0][i]?'T':'F')).join(', '));
console.log('Promise verified: exactly 1 solution out of 8 checked.');
} else {
setStatus(fmt('⚠ %length% solutions — promise would be violated!', { length: sols.length }), 'bad');
res.innerHTML = fmt('<strong>%length% satisfying assignments:</strong><br>', { length: sols.length });
sols.forEach(s => {
const row = document.createElement('div');
row.className = 'sol';
row.textContent = VARS.map((n, i) => n + '=' + (s[i] ? 'T' : 'F')).join(', ');
res.appendChild(row);
});
}
});
document.getElementById('reset').addEventListener('click', () => {
vals.fill(false);
setStatus('Toggle variables to build an assignment.', '');
document.getElementById('results').innerHTML = '';
render();
});
render();
<p class="hint">A <b>Unique-SAT</b> instance: this formula is <em>promised</em> to have exactly one satisfying assignment or none. Toggle each variable to build an assignment. Satisfied clauses turn green. Use <b>Find all solutions</b> to verify the promise.</p>
<div id="formula"></div>
<div id="controls"></div>
<div class="status" id="status">Toggle variables to build an assignment.</div>
<div class="btns">
<button id="check" type="button">Check assignment</button>
<button id="findAll" type="button">Find all solutions</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div id="results"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
#formula { display: flex; flex-direction: column; gap: 6px; margin-bottom: .8rem; }
.clause { display: flex; align-items: center; gap: 6px; font: 600 15px ui-monospace, monospace;
padding: 6px 10px; border-radius: 8px; background: #f0f3f6; border: 1.5px solid #d4dde6;
transition: background .2s, border-color .2s; }
.clause.sat { background: #d4f0de; border-color: #5baa76; }
.clause.unsat { background: #fde6e6; border-color: #d16060; }
.clause .lbl { font-size: .75rem; font-weight: 400; color: #888; width: 48px; flex-shrink: 0; }
.lit { color: #1d3557; }
.lit.neg { color: #6d4c41; }
.lit.true { color: #1a7a45; }
.lit.false { color: #b71c1c; }
.op { color: #888; font-weight: 400; margin: 0 2px; }
#controls { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: .8rem; }
.var-btn { display: flex; flex-direction: column; align-items: center; gap: 3px; cursor: pointer;
padding: 8px 14px; border-radius: 10px; border: 1.5px solid #adb1b8;
background: #e8ecf0; font: 700 15px ui-monospace, monospace; user-select: none;
transition: all .15s; min-width: 64px; }
.var-btn .val-label { font-size: .72rem; font-weight: 600; letter-spacing: .04em; }
.var-btn.true { background: #1d3557; color: #fff; border-color: #1d3557; }
.var-btn.true .val-label { color: #90caf9; }
.var-btn.false .val-label { color: #888; }
.status { font-size: 1rem; font-weight: 600; margin: .4rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
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; }
#results { font-size: .85rem; line-height: 1.6; color: #333; }
#results .sol { font-family: ui-monospace, monospace; background: #eef6ee;
border-left: 3px solid #5baa76; padding: 3px 8px; border-radius: 4px; margin: 2px 0; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Promise problem demo: Unique-SAT
// Formula (3 variables x1,x2,x3 – 4 clauses) with EXACTLY ONE solution.
// Promise: input is guaranteed to have 0 or 1 satisfying assignments.
// Unique solution (verified by exhaustive search): x1=true, x2=false, x3=true.
//
// Clauses (CNF) — all non-unit, found by exhaustive clause search:
// C1: x1 ∨ x2 blocks FFF, FFT (x1=F,x2=F)
// C2: x1 ∨ ¬x2 blocks FTF, FTT (x1=F,x2=T)
// C3: ¬x1 ∨ x2 ∨ x3 blocks TFF (x1=T,x2=F,x3=F)
// C4: ¬x1 ∨ ¬x2 blocks TTF, TTT (x1=T,x2=T)
// Together: only mask 5 = TFT = (x1=T,x2=F,x3=T) survives.
const VARS = ['x₁', 'x₂', 'x₃'];
const CLAUSES = [
{ label: 'C₁', lits: [{v:0,neg:false},{v:1,neg:false}] }, // x1 ∨ x2
{ label: 'C₂', lits: [{v:0,neg:false},{v:1,neg:true}] }, // x1 ∨ ¬x2
{ label: 'C₃', lits: [{v:0,neg:true}, {v:1,neg:false},{v:2,neg:false}] }, // ¬x1 ∨ x2 ∨ x3
{ label: 'C₄', lits: [{v:0,neg:true}, {v:1,neg:true}] }, // ¬x1 ∨ ¬x2
];
const vals = [false, false, false];
function clauseSat(clause, assignment) {
return clause.lits.some(l => assignment[l.v] !== l.neg);
}
function formulaSat(assignment) {
return CLAUSES.every(c => clauseSat(c, assignment));
}
function findAllSolutions() {
const sols = [];
for (let mask = 0; mask < 8; mask++) {
const a = [!!(mask & 4), !!(mask & 2), !!(mask & 1)];
if (formulaSat(a)) sols.push(a);
}
return sols;
}
function renderFormula() {
const div = document.getElementById('formula');
div.innerHTML = '';
CLAUSES.forEach(clause => {
const sat = clauseSat(clause, vals);
const row = document.createElement('div');
row.className = 'clause' + (sat ? ' sat' : ' unsat');
let html = `<span class="lbl">${clause.label}:</span> (`;
clause.lits.forEach((l, i) => {
const v = vals[l.v];
const litVal = l.neg ? !v : v;
const cls = 'lit' + (l.neg ? ' neg' : '') + (litVal ? ' true' : ' false');
html += `<span class="${cls}">${l.neg ? '¬' : ''}${VARS[l.v]}</span>`;
if (i < clause.lits.length - 1) html += '<span class="op">∨</span>';
});
html += ')';
row.innerHTML = html;
div.appendChild(row);
});
}
function renderControls() {
const div = document.getElementById('controls');
div.innerHTML = '';
VARS.forEach((name, i) => {
const btn = document.createElement('div');
btn.className = 'var-btn ' + (vals[i] ? 'true' : 'false');
btn.innerHTML = `<span>${name}</span><span class="val-label">${vals[i] ? 'TRUE' : 'FALSE'}</span>`;
btn.addEventListener('click', () => {
vals[i] = !vals[i];
setStatus('Toggle variables to build an assignment.', '');
document.getElementById('results').innerHTML = '';
render();
});
div.appendChild(btn);
});
}
function render() { renderFormula(); renderControls(); }
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + cls;
}
document.getElementById('check').addEventListener('click', () => {
if (formulaSat(vals)) {
setStatus('✓ All clauses satisfied — this is the unique solution!', 'ok');
} else {
const n = CLAUSES.filter(c => !clauseSat(c, vals)).length;
setStatus(fmt('✗ %n% clause%v% not satisfied. Keep trying.', { n: n, v: n > 1 ? 's' : '' }), 'bad');
}
document.getElementById('results').innerHTML = '';
});
document.getElementById('findAll').addEventListener('click', () => {
const sols = findAllSolutions();
const res = document.getElementById('results');
if (sols.length === 0) {
setStatus('No satisfying assignments found.', 'bad');
res.innerHTML = '<em>Promise holds: 0 solutions.</em>';
} else if (sols.length === 1) {
setStatus('✓ Exactly 1 solution — the promise holds!', 'ok');
const row = document.createElement('div');
row.className = 'sol';
row.textContent = fmt('Solution: %map%', { map: VARS.map((n, i) => n + '=' + (sols[0][i] ? 'T' : 'F')).join(', ') });
res.innerHTML = '<strong>All satisfying assignments (checked all 8):</strong><br>';
res.appendChild(row);
console.log('Unique solution:', VARS.map((n,i)=>n+'='+(sols[0][i]?'T':'F')).join(', '));
console.log('Promise verified: exactly 1 solution out of 8 checked.');
} else {
setStatus(fmt('⚠ %length% solutions — promise would be violated!', { length: sols.length }), 'bad');
res.innerHTML = fmt('<strong>%length% satisfying assignments:</strong><br>', { length: sols.length });
sols.forEach(s => {
const row = document.createElement('div');
row.className = 'sol';
row.textContent = VARS.map((n, i) => n + '=' + (s[i] ? 'T' : 'F')).join(', ');
res.appendChild(row);
});
}
});
document.getElementById('reset').addEventListener('click', () => {
vals.fill(false);
setStatus('Toggle variables to build an assignment.', '');
document.getElementById('results').innerHTML = '';
render();
});
render();
Comments
Loading comments...